简体   繁体   中英

Haskell IO - file lines to list

I am writing a haskell program and am having trouble with IO types. It seems something is wrong with my readLines function and the type it is supposed to return. I want it to return a list where each element is a line from the file that is opened with the openDict function.

Here is my code.

main :: IO ()
main = do
    fileHandle <- openDict
    readLines fileHandle
    putStr "End of program\n"

readLines :: Handle -> [IO String]
readLines fileHandle
    | isEOF <- hIsEOF fileHandle = []
    | otherwise                  = [hGetLine fileHandle] ++ readLines fileHandle

openDict :: IO Handle
openDict = do
    putStr "Enter file path: "
    filePath <- getLine
    openFile filePath ReadMode

Here is the error:

Couldn't match type `[]' with `IO'
    Expected type: IO (IO String)
      Actual type: [IO String]
    In the return type of a call of `readLines'
    In a stmt of a 'do' block: readLines fileHandle
    In the expression:
      do { fileHandle <- openDict;
           readLines fileHandle;
           putStr "End of program" }

So basically, how can I store these strings from IO in a list?

Your readLines function has several problems.

First, in this case you do not want to return a [IO String] . This would be a list of IO actions such as [print 1 >> return "a", print 2 >> return "b"] which is not what you want. You need a single IO action returning a list of String s, so you need to use IO [String] .

Second, the pattern guard isEof <- hIsEOF fileHandle is checking whether the value returned by hIsEOF (which is an IO action) is of the form isEOF , where isEOF is a variable being defined right now. Of course anything is of the form var , since a variable can be anything. So the test is always true, and not meaningful: it does not even run the IO action, for instance.

Third, the way you combine the list constructors and IO actions follows the current wrong type, so it is quite confusing.

A quite verbose way to achieve this would be instead:

readLines :: Handle -> IO [String]
readLines fileHandle = do
   eof <- hIsEOF fileHandle
   if eof then return []
          else do line <- hGetLine fileHandle
                  rest <- readLines fileHandle
                  return (line:rest)

The last lines can be shortened a bit using Applicative syntax:

import Control.Applicative
readLines2 :: Handle -> IO [String]
readLines2 fileHandle = do
   eof <- hIsEOF fileHandle
   if eof then return []
          else (:) <$> hGetLine fileHandle <*> readLines2 fileHandle   

A short version can be instead obtained by reading everything in the file and splitting into lists after that using the lines library function.

import Control.Applicative
readLines3 :: Handle -> IO [String]
readLines3 fileHandle = lines <$> hGetContents fileHandle

Here is the readLines that you likely really want.

readLines :: Handle -> IO [String]
readLines h = do
    isEOF <- hIsEOF h -- need to access isEOF directly, without monad, see comment about the bind
    case isEOF of
        True -> return [] -- the return adds the IO monad to the [String] type
        False -> do
            line <- hGetLine h -- need to access line separately without monad
            lines <- readLines h -- recurse down
            return $ line ++ lines -- return adds back the IO monad

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM