IO and fold (was Re: fold on Monad? )

Claus Reinke claus.reinke@talk21.com
Thu, 30 May 2002 10:25:27 +0100


>> foldr, foldM, etc. derive a recursive computation from the
>> recursive structure of an input list, so you have to feed
>> one in. If you want to bypass the list, you could use
>> IO-observations (getLine, isEOF) instead of list
>> observations (head/tail, null):
>
>Yes you can define it, 

And you can, as well. That's how common idioms come into being;
there's no special magic about the folds already in existence.
Just because people have done fold already for lists, trees, functors,..
doesn't mean that you won't have to define your own useful
abstractions every now and then. They evolve with input from theory
and practice, some forms survive, and become common.

>It seems to me that there's something odd about the way the
>IO monad interacts with bulk operations on files.  

That may be more related to IO than to the monadic approach
to it (try to think it over with a world-passing approach). And if
you really just want ot process text, there's interact, which worked
well enough for this purpose before IO became more general
and more controlable.

>In particular, it seems odd that getContents should ever be the
>easiest way of tackling something, 

Who says that? It's the laziest (as in: I don't want to invest any
time in this) way, and as it exists, it is often used for quick demos,
examples and explanations, e.g., here on the list.That doesn't mean 
you have (or want) to use it for all programs (e.g., lazy i/o has 
interesting interactions with concurrency:), and once you start 
using monadic style more, you'll develop useful abstractions for 
common idioms, just as it happened for list programming. Some 
of those are already in the standard libraries, but you'll still 
develop your own preferences. Haskell allows you to express
those, in libraries, usually without language extensions or help from
implementers. It only takes a little effort to get away from things
that look convenient, but may not be in the long run.

>rather than some natural operation on Monads. 

Monads are about the type constructor, >>=, and return.
Things like getLine have been thrown into this framework for 
practical use, but the theory won't tell you much about them
(you could take some concrete monads, and look at how
folds distribute over them, then try to find a general pattern;
the Meijer/Hutton "Bananas in Space" paper might be helpful).

>Doing something to each line of a file is such a common kind 
>of computation that it ought to be easy!

Who says it isn't?-) There are even several styles you could adopt,
including use of interact, or use of your own abstractions on top of 
io-monad operations..

>It also seems wrong that end of file should be an exception
>-- after all, for most files other than a terminal or
>"special file", having an end is the norm. As a result, the
>definition you gave strikes me as awkward (no fault of
>yours!). It suggests to me that a Monad isn't quite enough.

Again, nothing to do with monads - just my coding decision for that
example.. If you prefer, you can redefine it using IO.isEOF (I mentioned
that optimization was left to others:-).

>One of the great things about fold is that you don't have to
>code the test for the end: it's encapsulated in the
>higher-order function. Shouldn't there be the same for IO?

Isn't it?

    getContents = foldX (++) "" -- we drop the newlines here..
    getLines = foldX (:) []

No eof-testing in sight. Note that the list fold *definition* needs to 
test for the empty list as well (unless you restrict yourself to infinite 
lists) - why else would you pass in an initial value?

Just to make the comparison explicit (not tested):

foldr c n l = 
    if null l 
    then n 
    else let h = head l
               ft  = foldr c n (tail l)
           in c h ft

mif c t e = c >>= \b->if b then t else e

foldX c n = do
    mif isEOF 
        (return n)
        (do
                l <- getLine
                fls <- foldX c n
                return (c l fls)
        )

So we've mostly got a fold lifted into the io-monad, with getLine
delivering the "head" (and implicitly truncating further input to
the "tail"). What more do you want?-)

Hth,
Claus