Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

withFile vs. openFile

Tags:

haskell

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
like image 912
rickythesk8r Avatar asked Feb 23 '12 02:02

rickythesk8r


3 Answers

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.

like image 160
Lambda Fairy Avatar answered Sep 24 '22 02:09

Lambda Fairy


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.

like image 22
Reid Barton Avatar answered Sep 25 '22 02:09

Reid Barton


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.

like image 29
porges Avatar answered Sep 25 '22 02:09

porges