11

I'm currently working on project with Haskell, and have found myself some trouble. I'm supposed to read and insert into a list each line in a "dictionary.txt" file, but I can't seem to do so. I've got this code:

main = do
    let list = []
    loadNums "dictionary.txt" list

loadNums location list = do
    inh <- openFile location ReadMode
    mainloop inh list
    hClose inh

mainloop inh list = do 
    ineof <- hIsEOF inh
    if ineof
        then return ()
        else do 
            inpStr <- hGetLine inh
            inpStr:list
            mainloop inh list

It is supposed to get every line (I know it does get every line, since replacing the "inpStr:list" with a "putStrLn inpStr" works correctly, displaying all lines), and insert it into a list but I get the following error:

Couldn't match expected type `IO' against inferred type `[]'

Probably because the hGetLine isn't a String, but a IO String, which I have no idea how to handle in order to obtain a proper string I can insert in my list. I have no idea how this could be solved, or what the problem is exactly, but if anyone has any idea of how to properly get every line in a file into a list, I'd appreciate it.

Thanks in advance!

Sergio Morales
  • 2,600
  • 6
  • 32
  • 40

2 Answers2

15

Unless this is for homework or something, there's no reason to use so much effort. Reuse is lazy!

getLines = liftM lines . readFile

main = do
    list <- getLines "dictionary.txt"
    mapM_ putStrLn list

But as you seem to still be learning Haskell, it is important for you to understand what CesarB has written.

ephemient
  • 198,619
  • 38
  • 280
  • 391
  • When explaining things to someone who seems to still be learning Haskell, I'd avoid using (or even showing) the pointless style. Don't want to scare them ;-) – CesarB Oct 19 '08 at 01:51
  • 7
    +1 for mentioning `readFile`. It's so helpful to get out of the imperative mindset of open file/read line/detect EOF/close file, and let functions like `readFile`, `getContents` and `interact` handle the mess for you. – Nefrubyr Feb 15 '10 at 11:36
  • What does "mapM_" mean? I know "mapM" maps a function to monad.. but the one with the underscore? – Andriy Drozdyuk Dec 03 '11 at 06:40
  • @drozzy: `mapM_` is exactly the same as `mapM` except it ignores the result. So instead of getting a list of the results in the monad, you just `()` in it instead. Since `putStrLn` gives `IO ()` anyhow, with `mapM` you'd get `IO [()]`; `mapM_` just turns that into the `IO ()` that you actually want. – Tikhon Jelvis Dec 03 '11 at 12:10
  • I its been a while, I know, but just came across it. I'm also a haskell beginner and got a question. With your code calling main simply prints the contents of dictionary.txt to the screen. How can I have them in a list so that I'm able to do other things to that list? main is of type :: IO (). How do I get a list of strings, where each string is a line of dictionary.txt? thx – Max Feb 05 '12 at 16:29
  • @Max The `getLines :: String -> IO [String]` defined here would be fine. – ephemient Feb 06 '12 at 16:33
14

In the line where the error happens, Haskell is expecting "IO a", but you are giving it a []. Simplifying things a lot, on a do block on the IO monad, every line is either:

  • Something which returns a value of the "IO a" type; the value of the "a" type within it is discarded (so the "a" is often "()")
  • A <- expression, which does the same thing but instead of discarding the value of the "a" type gives it the name to the left of the <-
  • A let, which does nothing more than give a name to a value

In that do block, the "hGetLine inh" returns an "IO String", and the String within it is extracted and given the name inpStr. The next line, since it's neither a let or a <-, should have a type "IO a", which it doesn't (thus causing the compiler error). What you can do instead, since you already have the String, is a let:

let list' = inpStr:list

This creates a new list consisting of the String followed by the original list, and gives it the name of "list' ".

Change the following line to use "list' " instead of "list" (thus passing it the new list). That line calls (recursively) mainloop, which will read one more line, call itself, and so on. After reading the whole file, it will return something with the "IO ()" type. This "IO ()" will be returned to the do block at loadNums. Congratulations, you just created a list with the lines read from the file, in reverse order (since you were appending to the head of the list), and then did nothing to it.

If you want to do something to it, change the "return ()" to "return list"; the return will generate a value of type "IO [String]", with the list within it (return does nothing more than encapsulating the value), which you can extract at loadNums with the <- syntax.

The rest is left as an exercise to the reader.

CesarB
  • 43,947
  • 7
  • 63
  • 86