HfT 01 02 03 04 05 06     01 02 03 04 05 06 07 08 09 10 11 12 13 14        

module Main where

This is roughly what happens when we chain together our output using do notation. As Lisp is a practical form of the lambda calculus, Haskell is a practical form of category theory, and do notation is the camel’s nose under the tent.

Here, do notation is implicitly handling the argument passing of the preceding examples. IO changes the state of the world, so as functional programmers we imagine that we pass the current state of the world to each IO function, which returns the new state of the world after IO has taken place. In practice the relevant state of the world could consist only of our accumulated output. This passed state also serves to provide a timeline, keeping IO steps in their correct sequence; Haskell is otherwise free to evaluate expressions in any order it sees fit.

One uses do notation with monads. Monads are a generalization of this behind-the-scenes argument passing. IO is quite possibly the worst place to start learning about monads, but one needs IO to do anything, so there you are. For now, learn do notation by rote, but avoid thinking of it as an imperative break from our functional paradigm, as that’s pretty far from the truth.

IO is a monad, and () is the unit type; we use () when we need a type but there is no value. A compound type m a can be thought of as a wrapper m around a type a. Values of type m a may be used anywhere, but only functions that understand m get to look at a.

main  IO ()
main = do
   putStrLn "hello"
   putStrLn "there"
   putStrLn "world"
hello
there
world

Main.hs