This program produces the output I expect when given an input file of text delimited by \n:
import System.IO
main :: IO ()
main = do h <- openFile "test.txt" ReadMode
xs <- getlines h
sequence_ $ map putStrLn xs
getlines :: Handle -> IO [String]
getlines h = hGetContents h >>= return . lines
By substituting withFile for openFile and rearranging slightly
import System.IO
main :: IO ()
main = do xs <- withFile "test.txt" ReadMode getlines
sequence_ $ map putStrLn xs
getlines :: Handle -> IO [String]
getlines h = hGetContents h >>= return . lines
I manage to get no output at all. I'm stumped.
Edit: Not stumped anymore: thanks to one and all for the thoughtful and thought-provoking answers. I did a little more reading in the documentation and learned that withFile can be understood as a partial application of bracket.
This is what I ended up with:
import System.IO
main :: IO ()
main = withFile "test.txt" ReadMode $ \h -> getlines h >>= mapM_ putStrLn
getlines :: Handle -> IO [String]
getlines h = lines `fmap` hGetContents h
The file is being closed too early. From the documentation:
The handle will be closed on exit from withFile
This means the file will be closed as soon as the withFile
function returns.
Because hGetContents
and friends are lazy, it won't try to read the file until it is forced with putStrLn
, but by then, withFile
would have closed the file already.
To solve the problem, pass the whole thing to withFile
:
main = withFile "test.txt" ReadMode $ \handle -> do xs <- getlines handle sequence_ $ map putStrLn xs
This works because by the time withFile
gets around to closing the file, you would have already printed it.
Ugh, did no one ever give the simple solution?
main :: IO ()
main = do xs <- fmap lines $ readFile "test.txt"
mapM_ putStrLn xs
Don't use openFile
+hGetContents
or withFile
+hGetContents
when you can just use readFile
. With readFile
you can't shoot yourself in the foot by closing the file too early.
They do completely different things.
openFile
opens a file and returns a file handle:
openFile :: FilePath -> IOMode -> IO Handle
withFile
is used to wrap an IO computation that takes a file handle, ensuring that the handle is closed afterwards:
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
In your case, using withFile would look like this:
main = withFile "test.txt" ReadMode $ \h -> do
xs <- getlines h
sequence_ $ map putStrLn xs
The version you currently have will open the file, call getlines
, then close the file. Since getlines
is lazy, it won't get read any output while the file is open, and once the file is closed, it can't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With