我有一个格式[(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] }
这是一个很好的文本文件格式吗?那么我怎样才能读取该文本文件并将其转换回元组列表?
答案 0 :(得分:12)
在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 = ...
但这不应该是常见的。
答案 1 :(得分:5)
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感兴趣的人;我提供的示例来自论文:Haskel Do You Read Me?,另一种是 fast read
- 就像函数一样。
答案 2 :(得分:3)