Lazy IO is useful! [Was: "interact" behaves oddly...]

Jan-Willem Maessen jmaessen at MIT.EDU
Thu Oct 2 12:38:09 EDT 2003


Thomas Hallgren <hallgren at cse.ogi.edu> replies to Simon M:
> Simon Marlow wrote:
> 
> >The real problem is that lazy I/O injects side effects into the pure
> >world of expressions.  Haskell has a perfectly good system for
> >encapsulating side effects - the IO monad.  So why put these sneaky side
> >effects into pure values?
> >  
> >
> Although monadic IO is nice in many ways, I think of its introduction in 
> Haskell as a stopgap measure that solved the immediate problem of 
> interfacing Haskell to traditional operating system's imperative APIs, 
> but at the same time seems to have put a stop to further research into 
> the proper integration of laziness and IO.

I have to admit a great deal of sympathy for this view.  In Thomas's
example we use a file to buffer intermediate results.  I'd also note
that lazy I/O makes sense when I/O operations are *independent*.  For
example, consider the following strawman function:

> skipIt :: [String] -> [String] -> [Int]
> skipIt []     rs = map read rs
> skipIt (l:ls) rs = n : skipIt (drop n rs) ls
>   where n = read l

Here we consume the two inputs at different, but interrelated, rates.
If those inputs come from files, we'd like to read each file at
exactly the required rate.  Can we do this in the I/O monad?  In this
case, we probably can.  When skipIt is a page or more of
tightly-written code, it gets much harder---and we will inevitably
find ourselves creating the "IO" and "non-IO" versions of our
functions.  The expression/statement dichotomy strikes again.

Introducing eager evaluation makes the argument for lazy I/O *more*
compelling.  For example, we might eagerly evaluate a function which
consumes input from a file as long as there is input available.  No
more input?  Stop evaluating eagerly and be lazy.  Indeed, I speculate
[heh, heh] that we might be able to do away with threads for
concurrency in many cases if we choose an approach like this one.
After all, what is lazy evaluation but a mechanism for performing very
tightly-coupled concurrency?  Hybrid evaluation is simply a way to
loosen that coupling a bit.

Lots of fertile ground for future research here.  Indeed, replacing
threads with laziness could be a very compelling argument for using
laziness---even if you're one of those people who doesn't give a hang
about semantic purity.  

Yes, we'll need to think carefully about exceptions---but a good deal
of thought has already gone into asynchronous exceptions and the
Dynamic type, and having lazy I/O generate asynchronous exceptions of
type IOError may work out quite nicely.  Yes, we'll need to think more
carefully before providing functions like hGetContents---the real
mistake may be allowing use of the handle after the call.  And yes,
as always we'll need to be careful about space---we hope eagerness
will help.

By the way, Simon mentions that lazy I/O is complicated to implement
in the presence of eager evaluation.  I'll note that the real
complexity of eagerness comes from the expression langauge.  Once
you've figured out how to capture the semantics of infinite
computations, calls to error, and so forth, you've got a lot of
mechanism at your disposal.  Making that mechanism available to
implement lazy I/O is actually pretty easy (or at least, I found it
pretty easy in Eager Haskell).  There are other applications for this
machinery we've built.  We ought to seek them out.

-Jan-Willem Maessen
jmaessen at alum.mit.edu


More information about the Haskell-Cafe mailing list