Haskell returns lazy string from IO file

Here I am back again with (for me) really strange behavior of my new masterpiece ...

This code should read the file, but it doesn't:

readCsvContents :: String -> IO ( String )
readCsvContents fileName = do
     withFile fileName ReadMode (\handle -> do
          contents <- hGetContents handle
          return contents
          )

main = do
    contents <- readCsvContents "src\\EURUSD60.csv"
    putStrLn ("Read " ++ show (length contents) ++ " Bytes input data.")

      

Result

Read 0 Bytes input data.

      

Now I changed the first function and added putStrLn

:

readCsvContents :: String -> IO ( String )
readCsvContents fileName = do
     withFile fileName ReadMode (\handle -> do
          contents <- hGetContents handle
          putStrLn ("hGetContents gave " ++ show (length contents) ++ " Bytes of input data.")
          return contents
          )

      

and the result

hGetContents gave 3479360 Bytes of input data.
Read 3479360 Bytes input data.

      

WTF ??? Well, I know Haskell is lazy. But I didn't know I had to hit him in the ass this way.

+3


source to share


2 answers


You are right, this is pain. Avoid using the old standard I / O module, for this reason - except for simply reading a whole file, which won't change like you; this can be done simply with readFile

.

readCsvContents :: Filepath -> IO String
readCsvContents fileName = do
   contents <- readFile fileName
   return contents

      

Note that, according to the laws of the monad, this is exactly the same 1 as

readCsvContents = readFile

      



The problem with what you tried is that the handle is closed unconditionally when the monad exits withFile

without checking if the lazy evaluation actually contents

caused the file to be read. This is, of course, terrible; I would never use pens myself. readFile

Addresses issue by associating handle closure with source file garbage collection thunk; it's not very good, but often works well enough.

Check conduit or pipes to work properly with the IO file . The former focuses a little more on performance, while the latter focuses more on elegance (but it's not really that big of a difference).


1And your first try is the same as readCsvContents fn = withFile fn ReadMode hGetContents

.

+6


source


This is a problem with lazy IO. What's going on in your code is that it withFile

opens the file, passes the handle to the lambda. This lambda returns a lazy list containing the contents of the file. It then withFile

notices that the callback has completed and closes the file.

Since the returned list is lazy, the contents of the file will only be read when evaluating the list. This happens when called length

. However, at this point the file descriptor is already closed and therefore you cannot read anything from the file.



The modified version of your call forces the contents of the file in an argument withFile

, after which the file is still available and so it works.

+3


source







All Articles