Haskell: Написание текстовых файлов и анализ их обратно в исходный формат - PullRequest
8 голосов
/ 06 мая 2009

У меня есть список кортежей в формате [(String, String)], и мне нужна функция для записи содержимого списка в текстовый файл, затем другая функция для чтения этого текстового файла как тот же список кортежей , Вот что у меня есть для функции сохранения:

save :: Table -> IO()
save [] = writeFile "database.txt" ""
save zs = do { writeFile "database.txt" "" ; sequence_ [appendFile "database.txt" ("("++a++","++b++")\n") | (a,b) <- zs] }

Это был бы хороший формат для текстового файла? Тогда как я смогу прочитать этот текстовый файл и преобразовать его обратно в список кортежей?

Ответы [ 3 ]

12 голосов
/ 06 мая 2009

Определено в Prelude,

type ShowS = String -> String
class Show a where
    showsPrec :: Int -> a -> ShowS
    show :: a -> String
    showList :: [a] -> ShowS

type ReadS a = String -> [(a, String)]
class Read a where
    readsPrec :: Int -> ReadS a
    readList :: ReadS [a]
read :: (Read a) => String -> a

Короче говоря, это стандартные методы сериализации в Haskell. show :: (Show a) => a -> String может превратить все, что является экземпляром Show, в строку, а read :: (Read a) => String -> a может превратить строку во все, что является экземпляром Read (или сгенерировать исключение).

Большинство встроенных типов и структур данных в стандартной библиотеке имеют определенные экземпляры Show и Read; если вы составляете части из них, ваш тип также имеет определенные экземпляры Show и Read.

type Table = [(String, String)]

load :: (Read a) => FilePath -> IO a
load f = do s <- readFile f
            return (read s)

save :: (Show a) => a -> FilePath -> IO ()
save x f = writeFile f (show x)

Если Table был типом данных, вы должны запросить экземпляры, но вы можете попросить компилятор автоматически получить их для вас.

data Table = Table [(String, String)]
    deriving (Read, Show)

Иногда это невозможно, и вы должны определить свои собственные экземпляры.

instance Show Table where
    showsPrec p x = ...
instance Read Table where
    readsPrec p x = ...

Но это не должно быть обычным явлением.

5 голосов
/ 07 мая 2009

Подход show / read будет работать нормально, я тоже его использую, но только для небольших значений. При больших и более сложных значениях read будет очень медленным.

Этот надуманный пример демонстрирует плохую производительность read:

data RevList a = (RevList a) :< a | Nil
  deriving (Show, Read)

ghci> read "(((((((((((((((Nil)))))))))))))))" :: RevList Int

Кроме того, read не сможет прочитать некоторые допустимые выражения Haskell, особенно те, которые используют конструкторы инфиксов (например, :< в моем примере). Причина этого в том, что read не знает о фиксированности операторов. Именно поэтому show $ Nil :< 1 :< 2 :< 3 будет генерировать много, казалось бы, лишних скобок.

Если вы хотите использовать сериализацию для больших значений, я бы предложил использовать другую библиотеку, например Data.Binary . Это будет несколько сложнее, чем простой show, в основном из-за отсутствия deriving Binary. Однако существуют различные универсальные программные решения, которые дают вам deriving -подобные суррогаты.

Заключение: Я бы сказал, используйте решение show / read, пока не достигнете его пределов (возможно, как только вы начнете создавать реальные приложения), затем начните смотреть на что-то более масштабируемое (но также более сложный) как Data.Binary.


Примечание: для тех, кто интересуется парсерами и более продвинутыми вещами на Haskell Примеры, которые я привел, взяты из статьи: Хаскель Ты меня читаешь? , в качестве альтернативы быстрая read -подобная функция.

3 голосов
/ 06 мая 2009

С вашей текущей функцией у вас есть проблема, когда строки в списке содержат "," или ")", потому что это делает невозможным узнать, где заканчивается строка, когда вы пытаетесь снова прочитать данные. Вам необходимо каким-либо образом экранировать эти символы, когда они появляются в строке.

Гораздо проще использовать show и read, чтобы преобразовать ваши данные в строки и затем сделать это самостоятельно:

save :: Table -> IO ()
save zs = writeFile "database.txt" (show zs)

show экранирует специальные символы и гарантирует, что данные имеют формат, который может быть проанализирован с помощью read. Чтобы загрузить данные, вы должны прочитать файл в строку и передать его в read, чтобы преобразовать его в желаемую структуру данных.

...