Doyc
Doyc

Reputation: 11

How to use readFile

I am having trouble reading in a level file in Haskell. The goal is to read in a simple txt file with two numbers seperated by a space and then commas. The problem I keep getting is this: Couldn't match type `IO' with `[]'

If I understand correctly the do statement is supposed to pull the String out of the Monad.

readLevelFile :: FilePath -> [FallingRegion]
readLevelFile f = do
        fileContent <-  readFile f
        (map lineToFallingRegion (lines fileContent))

lineToFallingRegion :: String -> FallingRegion
lineToFallingRegion s = map textShapeToFallingShape (splitOn' (==',') s) 

textShapeToFallingShape :: String -> FallingShape
textShapeToFallingShape s = FallingShape (read $ head numbers) (read $ head 
$ tail numbers)
                      where numbers = splitOn' (==' ') s

Upvotes: 1

Views: 1577

Answers (2)

Mark Seemann
Mark Seemann

Reputation: 233125

You can't pull things out of IO. You can think of IO as a container (in fact, some interpretations of IO liken it to the box containing Schrödinger's cat). You can't see what's in the container, but if you step into the container, values become visible.

So this should work:

readLevelFile f = do
    fileContent <-  readFile f
    return (map lineToFallingRegion (lines fileContent))

It does not, however, have the type given in the OP. Inside the do block, fileContent is a String value, but the entire block is still inside the IO container.

This means that the return type of the function isn't [FallingRegion], but IO [FallingRegion]. So if you change the type annotation for readLevelFile to

readLevelFile :: FilePath -> IO [FallingRegion]

you should be able to get past the first hurdle.

Upvotes: 2

Thomas M. DuBuisson
Thomas M. DuBuisson

Reputation: 64740

Let's look at your first function with explicit types:

readLevelFile f = do
    (fileContent :: String) <-
                  (readFile :: String -> IO String) (f :: String) :: IO String

fileContent is indeed of type String but is only available within the execution of the IO Monad under which we are evaluating. Now what?

    (map lineToFallingRegion (lines fileContent))  :: [String]

Now you are suddenly using an expression that is not an IO monad but instead is a list value - since lists are also a type of monad the type check tries to unify IO with []. What you actually wanted is to return this value:

return (map lineToFallingRegion (lines fileContent)) :: IO [String]

Now recalling that we can't ever "exit" the IO monad your readLevelFile type must be IO - an honest admission that it interacts with the outside world:

readLevelFile :: FilePath -> IO [FallingRegion]

Upvotes: 1

Related Questions