[Haskell-cafe] IO

Ralf Hinze ralf at informatik.uni-bonn.de
Wed Dec 21 08:38:20 EST 2005


Interesting discussion ...

I don't think it is necessary/desirable to relegate IO to `chapter 14'.
First of all, IO is handled differently in Haskell as the language
designers quite successfully resisted the temptation of impurity.

However, you don't have to understand the full implications of
IO to be able to use it (like you don't have to know the full English
grammar to be able to speak English). I typically cover IO very early
in a Haskell course and then again in `chapter 14' after a discussion
of monads. (To motivate the need for a special treatment of IO, I often
ask the students to put themselves into the position of a language
designer who tries to add IO to a purely functional language.)

So why is there a special `IO' type? The type serves to distinguish
between values and effectful computations: `String' is the type of
strings, `IO String' is the type of computations that deliver a
string. This is why the `do' notation uses `<-' instead of `='.

> ask :: String -> IO String
> ask question = do { putStrLn question;
>                     answer <- getLine;
>                     putStrLn "Thanks!";
>                     return answer }

Here, `answer' of type `String' is the result of the effectful
computation `getLine' of type `IO String'. I think IO can be
treated on this level, just using the `do'-notation (it is not
necessary to explain that the do-notations is merely syntactic
sugar for `>>=' which involves higher-order functions).

One thing that is important to stress, however, is that `<-' is
a binder: the variable on the lhs is *bound* to value delivered
by the rhs. In other words, `<-' introduces a variable (it does
not assign a value to a store). Consequently, in 

	do { a <- m1; a <- m2; ... }

the second occurrence of `a' shadows the first occurrence.

It is useful to think of `IO' as a `description' of an effectful
computation (it is very much like a TODO list; the list describes
actions, which may or may not be executed). This is why something
of type `IO a' is also a value: it may be stored in a data structure
or passed to a function. This means you can program you own control
structures:

> data IOTree a = Done (IO a)
>               | Choice (IO Bool) (IOTree a) (IOTree a)
>
> execute :: IOTree a -> IO a
> execute (Done action)        
>   =  do { action }
> execute (Choice action left right)
>    =  do { b <- action;
>            if b then do { execute left }
>                 else do { execute right } }

Now, if IO is only a `description' of an IO action, when is the IO action
actually executed? Exactly when `main' is called: only the TODO list
that is bound to `main' is executed. Consider

> main = do { head [putStrLn "hello world", putStrLn "too lazy"] }

HTH, Ralf


More information about the Haskell-Cafe mailing list