Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: Writing text files and parsing them back to original format

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?

like image 477
benwad Avatar asked May 06 '09 14:05

benwad


3 Answers

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.

like image 59
ephemient Avatar answered Oct 22 '22 09:10

ephemient


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.

like image 5
Tom Lokhorst Avatar answered Oct 22 '22 08:10

Tom Lokhorst


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.

like image 3
sth Avatar answered Oct 22 '22 09:10

sth