IO monad and lazy evaluation

Graham Klyne
Tue, 20 May 2003 20:23:48 +0100

There seems to be a difficult-to-justify interaction between
lazy evaluation and monadic I/O:

-- file: SpikeIOMonadCloseHandle.hs
-- Does hClose force completion of lazy I/O?

import IO

showFile fnam =
     do  { fh <- openFile fnam ReadMode
         ; fc <- hGetContents fh
         ; hClose fh
         ; putStr fc

test = showFile "SpikeIOMonadCloseHandle.hs"

If I load this into Hugs and run it, the output is a single blank line.

If I reverse the order of hClose and putStr, the source code is displayed.

I think I can understand why this is happening, but it seems to me that there's
a violation of referential transparency here:  I can't see any reasonable
justification for the value of 'fc' to vary depending on whether it's actually
used before or after some other I/O operation.

I suppose I was expecting the call of hClose to force complete evaluation
of any value that depends on the state prior to hClose.  I've no idea if
there's a reasonable way to implement that.

My concern is that this weakens the claim for monads that they provide
a seamless integration between pure functional and stateful code;  cf.:
We believe that, on the contrary, there are very significant differences 
writing programs in C and writing in Haskell with monadic state 
transformers and
- Usually, most of the program is neither stateful nor directly concerned with
IO.  The monadic approach allows the graceful coexistence of a small amount
of imperative code and the large purely functional part of the program
- The usual coroutining behaviour of lazy evaluation, in which the consumer of
a data structure coroutines with its producer, extends to stateful computation
as well.  As Hughes argues (Hughes 1989), the ability to separate what is
computed from how much of it is computed is a powerful aid to writing modular


Graham Klyne
PGP: 0FAA 69FF C083 000B A2E9  A131 01B9 1C7A DBCA CB5E