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.