
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