views:

365

answers:

3

I'm trying a little experiment in haskell, wondering if it is possible to exploit laziness to process IO. I'd like to write a function that takes a String (a list of Chars) and produces a string, lazily. I would like then to be abily to lazily feed it characters from IO, so each character would be processed as soon as it was available, and the output would be produced as the characters necessary became available. However, I'm not quite sure if/how I can produce a lazy list of characters from input inside the IO monad.

+8  A: 

Regular String IO in Haskell is lazy. So your example should just work out of the box.

Here's an example, using the 'interact' function, which applies a function to a lazy stream of characters:

interact :: (String -> String) -> IO ()

Let's filter out the letter 'e' from the input stream, lazily (i.e. run in constant space):

main = interact $ filter (/= 'e')

You could also use getContents and putStr if you like. They're all lazy.

Running it to filter the letter 'e' from the dictionary:

$ ghc -O2 --make A.hs
$ ./A +RTS -s < /usr/share/dict/words
...
               2 MB total memory in use (0 MB lost due to fragmentation)
...

so we see that it ran in a constant 2M footprint.

Don Stewart
You can also see this effect at the command line. Run Don's 'A' program in the shell/Terminal/whatever your OS uses, and type text straight into it. Assuming it's line buffered, as you press enter after each line you'll see the filtered text printed immediately, even though the program appears to only make one "call" to `filter`.
Nefrubyr
+2  A: 
unsafeInterleaveIO :: IO a -> IO a

unsafeInterleaveIO allos IO computation to be deferred lazily. When passed a value of type IO a, the IO will only be performed when the value of a is demanded. This is used to implement lazy file reading, see System.IO.hGetContents.

For example, main = getContents >>= return . map Data.Char.toUpper >>= putStr is lazy; as you feed characters to stdin, you will get characters on stdout.

(This is the same as writing main = interact $ map Data.Char.toUpper, as in dons's answer.)

ephemient
+3  A: 

The simplest method of doing lazy IO involves functions such as interact, readFile, hGetContents, and such, as dons says; there's a more extended discussion of these in the book Real World Haskell that you might find useful. If memory serves me, all such functions are eventually implemented using the unsafeInterleaveIO that ephemient mentions, so you can also build your own functions that way if you want.

On the other hand, it might be wise to note that unsafeInterleaveIO is exactly what it says on the tin: unsafe IO. Using it--or functions based on it--breaks purity and referential transparency. This allows apparently pure functions (that is, that do not return an IO action) to effect the outside world when evaluated, produce different results from the same arguments, and all those other unpleasant things. In practice, most sensible ways of using unsafeInterleaveIO won't cause problems, and simple mistakes will usually result in obvious and easily diagnosed bugs, but you've lost some nice guarantees.

There are alternatives, of course; you can find assorted libraries on Hackage that provide restricted, safer lazy IO or conceptually different approaches. However, given that problems arise only rarely in practical use, I think most people are inclined to stick with the built-in, technically unsafe functions.

camccann