1

I am having trouble converting a list of strings from a text file to a data type I have created called Film. I will display the code below:

Inside the films.txt

"Casino Royale" 
"Daniel Craig", "Eva Green", "Judi Dench"
2006
"Garry", "Dave", "Zoe", "Kevin", "Emma"

"Cowboys & Aliens"
"Harrison Ford", "Daniel Craig", "Olivia Wilde"
2011
"Bill", "Jo", "Garry", "Kevin", "Olga", "Liz"

"Catch Me If You Can"
"Leonardo DiCaprio", "Tom Hanks"
2002
"Zoe", "Heidi", "Jo", "Emma", "Liz", "Sam", "Olga", "Kevin", "Tim"
}

My Haskell code:

type Title = String
type Actor = String
type Cast = [Actor]
type Year = Int
type Fan = String
type Fans = [Fan]

type Film = (Title, Cast, Year, Fans)

main ::  IO()
main = do 
      putStr "What is your name?: "
      name <- getLine
      firstdatabase <- readFile "films.txt"
      putStr firstdatabase
      let database = read firstdatabase :: [Film]
      mainLoop database name

Any ideas on what I will have to do?

3
  • 3
    There are parsing libraries like parsec that can do this sort of thing. It wouldn't be very difficult to write a parser for a Film. Unfortunately you won't get very far with read here, since the Read instances for the types you have expect literal haskell values, so you'd need a 4-tuple (with parens) in your database file with a string (with quotes), a list of strings (with brackets), an int, then a list of strings (with brackets), all separated by commas, and then you would need the entire contents of the file to be capable of being interpreted as a literal haskell list, etc etc. Commented Mar 17, 2015 at 19:51
  • 2
    In short, read does not make a good parser. Commented Mar 17, 2015 at 19:51
  • @bheklilr: true, though it would also be quite simple to convert that format to Haskell code, and then parse that with read. Not really elegant of course, but oh well... Commented Mar 17, 2015 at 20:17

1 Answer 1

4

This can be accomplished pretty easily with parsec:

import Text.Parsec
    ( Parsec, ParseError, parse        -- Types and parser
    , between, noneOf, sepBy, many1    -- Combinators
    , char, spaces, digit, newline     -- Simple parsers
    )

-- Parse a string to a string
stringLit :: Parsec String u String
stringLit = between (char '"') (char '"') $ many1 $ noneOf "\"\n"

-- Parse a string to a list of strings
listOfStrings :: Parsec String u [String]
listOfStrings = stringLit `sepBy` (char ',' >> spaces)

-- Parse a string to an int
intLit :: Parsec String u Int
intLit = fmap read $ many1 digit
-- Or `read <$> many1 digit` with Control.Applicative

film :: Parsec String u Film
film = do
    -- alternatively `title <- stringLit <* newline` with Control.Applicative
    title <- stringLit
    newline
    cast <- listOfStrings
    newline
    year <- intLit
    newline
    fans <- listOfStrings
    newline
    return (title, cast, year, fans)
-- Alternatively, you can define it all in one go (with Control.Applicative) as
-- film = (,,,)
--      <$> stringlit     <* newline
--      <*> listOfStrings <* newline
--      <*> intLit        <* newline
--      <*> listOfStrings <* newline
-- Which makes it look very much like your actual file

films :: Parsec String u [Film]
films = film `sepBy` newline

Then you can use it as

loadDB :: FilePath -> IO (Either ParseError [Film])
loadDB filename = do
    db <- readFile filename
    return $ parse films "films" db

main :: IO ()
main = do
    putStr "What is your name?"
    name <- getLine
    db' <- loadDB "films.txt"
    case db of
        Left err -> do
            putStrLn "Error loading film database:"
            print err
        Right db -> mainLoop db name
Sign up to request clarification or add additional context in comments.

2 Comments

I like this answer very much. I think in this case, it would be a little more useful to see all this with a qualified import, even for the data and value constructors. Hard enough to understand this if you're new to Haskell, let alone with needing to constantly jump back and forth from the code to the module source code to see what functions come from where.
@Mr.F Better? I believe I got all the Parsec imports included as explicit imports, hopefully I didn't miss one.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.