I have a list of tuples of format [(String,String)] and I need a function to write the contents of the list to a text file, then another function to read this text file in as the same list of tuples. Here's what I've got for the saving function:
save :: Table -> IO()
save [] = writeFile "database.txt" ""
save zs = do { writeFile "database.txt" "" ; sequence_ [appendFile "database.txt" ("("++a++","++b++")\n") | (a,b) <- zs] }
Would that be a good format for the text file? Then how would I be able to read that text file in and convert it back to the list of tuples?
Defined in 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
In short, these are the standard "serialization" methods in Haskell. show :: (Show a) => a -> String
can turn anything that is an instance of Show
into a string, and read :: (Read a) => String -> a
can turn a string into anything that is an instance of Read
(or throw an exception).
Most of the built-in types and data structures in the standard library have Show
and Read
instances defined; if you're composing parts from them, your type also has Show
and Read
instances defined.
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)
If Table
were a datatype, you have to ask for the instances, but you can request that the compiler automatically derive them for you.
data Table = Table [(String, String)]
deriving (Read, Show)
Sometimes that's not possible and you have to define your own instances.
instance Show Table where
showsPrec p x = ...
instance Read Table where
readsPrec p x = ...
But that shouldn't be common.
The show
/read
approach will work fine, I use it as well, but only for small values. On larger, more complex values read
will be very slow.
This contrived example demonstrates the bad performance of read
:
data RevList a = (RevList a) :< a | Nil
deriving (Show, Read)
ghci> read "(((((((((((((((Nil)))))))))))))))" :: RevList Int
Also, read
won't be able to read some valid Haskell expressions, especially ones that use infix constructors (like the :<
in my example). The reason for this is that read
is unaware of the fixity of operators. This is also why show $ Nil :< 1 :< 2 :< 3
will generate a lot of seemingly redundant parentheses.
If you want to have serialization for bigger values, I'd suggest to use some other library like Data.Binary. This will be somewhat more complex than a simple show
, mainly because of the lack of deriving Binary
. However, there are various generic programming solutions to give you deriving
-like surrogates.
Conclusion: I'd say, use the show
/read
solution until you reach its limits (probably once you start building actual applications), then start looking at something more scalable (but also more complex) like Data.Binary.
Side note: To those interested in parsers and more advanced Haskell stuff; The examples I gave came from the paper: Haskel Do You Read Me?, on an alternative, fast read
-like function.
With your current function you have a problem when the strings in the list contain "," or ")" because that makes it impossible to find out where a string ends when you try to read the data in again. You would need to escape these characters in some way whenever they appear in a string.
It's much easier to use show
and read
to convert your data to strings and back then to do it on your own:
save :: Table -> IO ()
save zs = writeFile "database.txt" (show zs)
show
escapes special characters and makes sure the data is in a format that can be parsed by read
. To load the data you would read the file into a string and pass this to read
to convert it to your desired data structure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With