1

In my current working directory there is a file named test.txt, which contains "Test\n".

With System.IO.readFile, GHCI returns the content:

Prelude System.IO> readFile "test.txt"
"Test\n"

But not so with the following, which should be equal in my opinion:

Prelude System.IO> withFile "test.txt" ReadMode hGetContents
""

Why is it not the case? How to get the whole file contents within the withFile IO action?

ominug
  • 1,422
  • 2
  • 12
  • 28

1 Answers1

3

TL;DR: Lazy IO is evil.

What happens is that hGetContents returns an IO-lazy list of the file contents. This means that the file handle will be read only when said list is actually accessed. Then the control passes to withFile which closes the file handle. Finally, the result is printed, and the list is demanded: only now a read is performed on the handle. Alas, it's too late.

As an ugly, manual "flush" of this laziness, you can try e.g.

hGetCont handle = do
   c <- hGetContents handle
   length c `seq` return c

The above forces the length of the list to be computed, hence forcing the whole file to be read. Reid Barton below suggest more beautiful alternatives, which avoid the use of the horribly evil lazy IO.

chi
  • 111,837
  • 3
  • 133
  • 218
  • Thanks, but this only answers the first question. Is there a way to flush this laziness? – ominug Jun 30 '15 at 18:43
  • 1
    @ominug, you should just not use `withFile` and `hGetContents` together. Use `readFile`, or `openFile` and `hGetContents`. – Reid Barton Jun 30 '15 at 18:47
  • @reid-barton But what can then be used with `withFile`? – ominug Jun 30 '15 at 18:50
  • Strict IO functions like `hGetLine`. – Reid Barton Jun 30 '15 at 18:53
  • @reid-barton Ok. But `withFile` is just `openFile` and `hClose` around a lambda. I think this really breaks the notion of reproducible/constant values. So is there a recommendation to always handle `hGetContents` with care? – ominug Jun 30 '15 at 18:55
  • 2
    Lazy IO as implemented in Haskell is evil. There's nothing about the concept of `hGetContents` that forces it to be implemented that way. – Jonathan Cast Jun 30 '15 at 19:25
  • 3
    You should not manually close a handle (including via `withFile`) that you have passed to `hGetContents` (unless you are sure you have already consumed as much of the file as you will ever need). If you consume the entire file, `hGetContents` will close the file automatically for you. – Reid Barton Jun 30 '15 at 19:25