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