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.
source to share
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
.
source to share
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.
source to share