views:

2056

answers:

2

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!

+8  A: 

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
+5  A: 

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
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
+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