Haskell Programmer's Guide IO Monad: The To The
Haskell Programmer's Guide IO Monad: The To The
— Don’t Panic —
Stefan Klinger
University of Twente, the Netherlands EWI, Database Group
CTIT Technical Report
Stefan Klinger.
The Haskell Programmer’s Guide to the IO Monad — Don’t Panic.
Order-address:
Centre for Telematics and Information Technology
University of Twente
P. O. Box 217
7500 AL Enschede
the Netherlands
mailto:[email protected]
All rights reserved. No part of this Technical Report may be reproduced, stored in a database
or retrieval system or published in any form or in any way, electronically, by print, photoprint,
microprint or any other means, without prior written permission from the publisher.
Stefan Klinger. The Haskell Programmer’s Guide to the IO Monad — Don’t Panic. Technical re-
port, December 2005, no. 05-54, 33 pp., Centre for Telematics and Information Technology (CTIT),
ISSN 1381-3625.
Now, that you have started with Haskell, have you written a program doing IO yet, like reading a
file or writing on the terminal? Then you have used the IO monad — but do you understand how
it works?
The standard explanation is, that the IO monad hides the non-functional IO actions —which do
have side effects— from the functional world of Haskell. It prevents pollution of the functional
programming style with side effects.
However, since most beginning Haskell programmers (i.e., everyone I know and including me)
lack knowledge about category theory, they have no clue about what a monad really is. Nor
how this “hiding” works, apart from having IO actions disappearing beyond the borders of our
knowledge.
This report scratches the surface of category theory, an abstract branch of algebra, just deep
enough to find the monad structure. On the way we discuss the relations to the purely func-
tional programming language Haskell. Finally it should become clear how the IO monad keeps
Haskell pure.
We do not explain how to use the IO monad, nor discuss all the functions available to the pro-
grammer. But we do talk about the theory behind it.
Intended audience Haskell programmers that stumbled across the IO monad, and now want to
look under the hood. Haskell experience and the ability to read math formulae are mandatory.
Many thanks to Sander Evers, Maarten Fokkinga, and Maurice van Keulen for reviewing, a lot
of discussions, helpful insights and suggestions.
3
Contents
Preface 3
Contents 4
1 Introduction 5
1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Related work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 Notation 6
3 Categories 7
3.1 Categories in theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2 Spot a category in Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
4 Functors 9
4.1 Functors in theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
4.2 Functors implement structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.3 Functors in Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5 Natural Transformations 13
5.1 Natural transformations in theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
5.2 Natural transformations in Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
5.3 Composing transformations and functors . . . . . . . . . . . . . . . . . . . . . . . . 14
6 Monads 17
6.1 Monads in theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
6.2 Monads in Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
6.3 An alternative definition of the monad . . . . . . . . . . . . . . . . . . . . . . . . . . 21
6.4 Haskell’s monad class and do-notation . . . . . . . . . . . . . . . . . . . . . . . . . . 26
6.5 First step towards IO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
6.6 The IO monad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Final remarks 33
Bibliography 34
4
Chapter 1
Introduction
1.1 Motivation
Imagine you want to write a somewhat more sophisticated “hello world” program in Haskell,
which asks for the user’s name and then addresses him personally.
You probably end up with something like
main :: IO ()
main = do putStr "What’s your name?nn> "
x <- getLine
putStr . concat $ [ "Hello ", x, ".nn" ]
So what exactly does the “do” mean? Is “<-” variable assignment? And what does the type “IO
()” mean?
Having accepted the presence of “IO ()” in the type signatures of basic IO functions (like, e.g.,
putStr, getChar), a lot of novice Haskell programmers try to get rid of it as soon as possible:
“I’ll just wrap the getLine in another function which only returns what I really need, maybe
convert the users’ input to an Int and return that.”
However, this is not possible since it would violate the referential transparency Haskell enforces
for all functions. Referential transparency guarantees that a function, given the same parameters,
always returns the same result.
How can IO be done at all, if a function is referential transparent and must not have side effects?
Try to write down the signatures of such a function that reads a character from the keyboard, and
a function that writes a character to the screen, both without using the IO monad.
The intention of this guide is to show how the Monad helps in doing IO in a purely functional
programming language, by illuminating the mathematical background of this structure.
5
Chapter 2
Notation
I tried to keep the notation as readable as possible, yet unambiguous. The meaning should be
clear immediately, and I hope that predicate logic people excuse some lack of purity.
1. Quite often proofs are interspersed with explanatory text, e.g., talking about integers we
might note
a+b
= operator ‘+’ is commutative
b+a .
2. Function application is always noted in juxtaposition —i.e., the operand is just written be-
hind the function— to avoid a Lisp-like amount of parenthesis, i.e.
fx
by definition
f (x) .
3. Quantifiers have higher precedence than and and or (symbols ^ ; _ ), but lower than implica-
tion and equivalence (); ,).
A quantifier ”binds” all the free variables in the following ‘;’-separated list of predicates
that are neither given in the context of the formula (constants), nor bound by an earlier
quantifier. I.e.,
8 m;n 2 N ; m < n 9 r 2 R m<r<n
next line is predicate logic
8m8n m 2 N ^ n 2 N ^ m < n ) 9r(r 2 R ^ m < r < n) .
4. Sometimes the exercise sign (✎) occurs, requesting the reader to verify something, or to
play with the Haskell code given.
6
Chapter 3
Categories
It is common practice to write f : A ! B to denote that a function f maps from set A to set
B . This is called the type of the function.
Also, we can compose two functions f and g , if the target set of the former equals the source
set of the latter, i.e., if f : A ! B and g : B ! C for some sets A, B , and C . The composition
is commonly denoted by g f .
For each set A, there is an identity function idA :A ! A.
These are the properties of a category. While most things in this guide can be applied to the
category of sets, Definition 3.1.1 is more precise and general. In particular, category theory is not
restricted to sets and total functions, i.e., one can not assume that an object has elements (like sets
do), or that an element is mapped to another element.
7
CHAPTER 3. CATEGORIES
Morphisms can be composed whenever the target of the first equals the source of the
second. Then the resulting morphism maps from the source of the first to the target of the
second:
f :A !B^g:B !C ) gf :A!C
In the following, the notation g f implies these type constraints to be fulfilled.
Composition is associative, i.e. f (g h) = (f g ) h.
5. Each object A 2 O has associated a unique identity morphism idA . This is denoted by
defining the function
Id : O ! M
A 7 ! idA ,
which automatically implies uniqueness.
The typing of the identity morphisms adheres to
8A2O idA :A !A .
To deserve their name, the identity morphisms are —depending on the types— left or
right neutral with respect to composition, i.e.,
f idsrc f = f = idtgt f f .
Another example Every set A with a partial order () on it yields a category, say Q. To see this,
call the elements of A objects, and the relation morphisms:
OQ := A ,
MQ := f(x; y) j x; y 2 A ^ x yg .
Now (✎ as an exercise) define the type information TQ , the composition Q and the identities IdQ ,
so that Q is a category indeed. Note, that this example does not assume the objects to have any
members.
More examples and a broader introduction to category theory (however, without discussing the
monad structure) can be found in [1].
id :: forall a. a -> a
corresponding to
8 A 2 OH idA :A !A .
Note that we do not talk about n-ary functions for n 6= 1. You can consider a function like (+) to
map a number to a function that adds this number, a technique called currying which is widely
used by Haskell programmers. Within the context of this guide, we do not treat the resulting
function as a morphism, but as an object.
8
Chapter 4
Functors
4.1.2 Notation Unless it is required to refer to only one of the mappings, the subscripts M and O
are usually omitted. It is clear from the context whether an object or a morphism is mapped by
the functor.
Due to this definition, we can write GF A and GF f without ambiguity. Multiple application of
the same mapping is often noted with a superscript, i.e., we define F 2 := F F for any functor F .
9
CHAPTER 4. FUNCTORS
4.1.5 Lemma The identity and composition just defined yield functors again: In the situation of
Definition 4.1.4,
IA : A!A , GF : A!C , IB F = F = F IA .
Hence, functor application on an object is a type level operation in Haskell, while functor applica-
tion on a morphism is a value level operation.
Haskell comes with the definition of a class
10
Functors in Haskell 4.3
✎ Working examples are the List or Maybe structures (see Lemma 4.3.1). Both are members of the
functor class, so you can enter the following lines into your interactive Haskell interpreter. The
:t prefix causes Hugs and GHCi to print the type information of the following expression. This
might not work in every environment. Understand the results returned by the Haskell interpreter.
:t fmap ord
fmap ord ""
fmap ord "lambda"
:t fmap chr
fmap chr Nothing
fmap chr (Just 42)
The examples illustrate how the functions ord and chr (from Haskell’s Char module) are lifted
to work on Lists and Maybes instead of just atomic values.
You might stumble across the question what data constructors like Just, Nothing,
(:) and [] actually are. At this point we are leaving category theory, and look into
the objects which indeed turn out to have set properties in Haskell.
A sound discussion of Haskell’s type system is far beyond the scope of this little guide,
so just imagine the data constructors to be some markers or tags to describe a member
of an object. I.e., Just "foo" describes a member of the Maybe String object, as
does the polymorphic Nothing.
The mapping function fmap differs from functor to functor. For the Maybe structure, it can be
written as
Note, however, that having a type being a member of the Functor class does not imply to have
a functor at all. Haskell does not check validity of the functor properties, so we have to do it by
hand:
which must be checked for each type one adds to the class. Note, that Haskell overloads
the id function: The left occurrence corresponds to idA , while the right one corresponds to
idF A .
3. F (g f ) = F g F f translates to
fmap (g . f) == fmap g . fmap f
which has to be checked, generalising over all functions g and f of appropriate type.
One should never add a type constructor to the Functor class that does not obey these laws,
since this would be misleading for people reading the produced code.
11
CHAPTER 4. FUNCTORS
I.e., they are not only members of the class, but also behave as expected in theory. The proof is
easy (✎), and when ever you want to add a type to the functor class, you have to perform this
kind of proof. So do this as an exercise before reading ahead.
4.3.2 Proof The proof of Lemma 4.3.1 strictly follows the structure of the Haskell types, i.e., we
differentiate according to the data constructors used.
For the Maybe structure, the data constructors are Just and Nothing. So we prove the second
functor property, F idA = idF A , by
fmap id Nothing
== Nothing
== id Nothing
fmap id (Just y)
== Just (id y)
== Just y
== id (Just y) ,
Note, that List is —in contrast to Maybe— a recursive structure. This can be observed in the
according definition of fmap above, and it urges us to use induction in the proof: The second
property, F idA = idF A , is shown by
fmap id []
== []
== id []
fmap id (x:xs)
== (id x):(fmap id xs)
== (id x):(id xs) --here we use induction
== x:xs
== id (x:xs) ,
fmap (g . f) (x:xs)
== ((g . f) x):(fmap (g . f) xs)
== ((g . f) x):((fmap g . fmap f) xs) --induction
== (g (f x)):(fmap g (fmap f xs))
== fmap g ((f x):(fmap f xs))
== fmap g (fmap f xs)
== (fmap g . fmap f) xs .
12
Chapter 5
Natural Transformations
8 f : A A! B
B B F f = Gf B A .
A
F AaC O / GA
CC {=
CC {{{ The definition says, that a natural transformation transforms
C {
F CC ; {{ G from a structure F to a structure G, without altering the be-
_ {
A haviour of morphisms on objects. I.e., it does not play a role
whether a morphism f is lifted into the one or the other struc-
Ff f Gf
ture, when composed with the transformation.
;B In the drawing on the left, this means that the outer square com-
{ _ CCC
F {{{ CCG
mutes, i.e., all directed paths with common source and target are
{{ CC
}{{ C! equal.
/ GB
FB B
13
CHAPTER 5. NATURAL TRANSFORMATIONS
To spot a transformation here, we still miss a functor on the source side of Just’s type. This is
overcome by adding the identity functor IH , which leads to the transformation
Just : IH ! Maybe .
5.2.2 Proof Let f::A->B be an arbitrary Haskell function. Then we have to prove, that
Just . IH f == fmap f . Just
where the left Just refers to B , and the right one refers to A . In that line, we recognise the
definition of the fmap for Maybe (just drop the IH ).
Another example, this time employing two non-trivial functors, are the maybeToList and list-
ToMaybe functions. Their definitions read
Note, that the loss of information imposed by applying listToMaybe on a list with more than one
element does not contradict naturality, i.e., forgetting data is not altering data.
We do not go through the proof of their naturality here (✎ but, of course, you can do this on your
own). Instead, we discuss some more interesting examples in the next chapter (see Lemma 6.2.1).
Natural transformations and functors Just as in Definition 4.1.4, we can define a composition
between a natural transformation and a functor. Therefore, an object is first lifted by the functor
and then mapped to a morphism by the transformation, or it is first mapped to a morphism which
is then lifted:
Again, due to the above definition, we can write EA and HB (for A and B objects in the
respective category) without ambiguity. The following pictures show the situation:
OA DD E / OB OB FF
DD FFH
DD FF
E DD FF
" "
MC MC H
/ MD
5.3.2 Lemma In the situation of Definition 5.3.1,
H : HF !
_ HG and E : F E !
_ GE
14
Composing transformations and functors 5.3
. Part I
:F
!
_ G,
) definition of naturality
8 f : A B! B B F f = Gf A
)
8 f : A B! B H (B F f ) = H (Gf A)
) functor property
8 f : A B! B H (B ) H (F f ) = H (Gf ) H (A)
) Definition 5.3.1
8 f : A B! B (H)B (HF )f = (HG)f (H)A
) definition of naturality
H : HF ! _ HG ,
. Part II
:F
!
_ G
) definition of naturality
8 f : A B! B F f = Gf A
B
) choose f from category A
8 f : A A! B (EB ) F (Ef ) = G(Ef ) (EA)
) Definition 5.3.1
8 f : A A! B (E )B (F E )f = (GE )f (E )A
) definition of naturality
E : F E ! _ GH .
Composing natural transformations Even natural transformations can be composed with each
other. Since, however, transformations map objects to morphisms —instead of, e.g., objects to
objects— they can not be simply applied one after another. Instead, composition is defined com-
ponent wise, also called vertical composition.
FA
/ A The picture on the left (with A 2 OA ) clarifies why this compo-
3 sition is called vertical.
A GA A
All compositions defined before Definition 5.3.4 are called hor-
/ A izontal. ✎ Why?
HA
15
CHAPTER 5. NATURAL TRANSFORMATIONS
hold.
16
Chapter 6
Monads
(Mind, that the parenthesis group composition of natural transformations and functors. They do
not refer to function application. This is obvious due to the types of the expressions in question.)
The transformations and are somewhat contrary: While adds one level of structure (i.e.,
functor application), removes one. Note, however, that transforms to F , while transforms
from F 2 . This is due to the fact that claiming the existence of a transformation from a functor F
to the identity IC would be too restrictive:
Consider the set category S and the list endofunctor L. Any transformation from L to IS has to
map every object A to a morphism A , which in turn maps the empty list to some element in A,
i.e.,
: L ! IS
)
8 A 2 OS A : LA ! A
)
8 A 2 OS 9 c 2 A A [ ] = c ,
where [ ] denotes the empty list. This, however, implies
8 A 2 OS A= 6 ; .
The following drawings are intended to clarify Definition 6.1.1: The first equation of the definition
is equivalent to
8A2O A F A = A F A .
So what are F A and F A? You can find them at the top of these drawings:
F A F A
F 3A O / F 2A F 3A O / F 2A
F
_
A _
F 2A O / FA F OA
F
_ _
A A
Since application of A unnests a nested structure by one level, F A pushes application of this
unnesting one level into the nested structure. We need at least two levels of nesting to apply A.
17
CHAPTER 6. MONADS
So we need at least three levels of nesting to push the application of A one level in. This justifies
the type of F A.
The morphism F A, however, applies the reduction on the outer level. The F A only assures that
there is one more level on the inside which, however, is not touched.
Hence, F A and F A depict the two possibilities to flatten a three-level nested structure into
a two-level nested structure through application of a mapping that flattens a two-level nested
structure into an one-level nested structure.
The statement A F A = A F A says, that after another step of unnesting (i.e., A), it is
irrelevant which of the two inner structure levels has been removed by prior unnesting (i.e., F A
or F A).
Let us have a look at the second equation as well. It is equivalent to
8 A 2 OC A F A = idF A = A F A .
Again, we examine F A and F A, which are at the top of the drawings.
F A F A
FA O / F 2A FA N / F 2A
F
_
A ^
A N / FA F OA
F
^ _
A A
Since A adds one level of nesting to A, the morphism F A imposes this lifting inside a flat
structure (F A), yielding a nested structure F 2 A.
Also, F A adds one level of nesting, however on the outside of a flat structure.
I.e., similar to the situation above, F A and F A reflect the two ways to add one level of nesting
to a flat structure: It can be done by lifting the whole structure, or by lifting the things within the
structure.
So A F A = idF A = A F A states, just as for the first equation, that after unnesting (i.e.,
A) it is not relevant whether additional structure has been added on the outside (F A) or the
inside (F A) of a flat structure F A. Additionally, it says that nesting followed by unnesting, is
the identity.
LA applies the concatenation of a list of lists to the outermost list of lists. Here, A refers to the
object LN corresponding to the third-level list structure, which is the topmost level in the
nested structure that is not effected by the transformation.
Application of LA returns a new list, containing all the unchanged innermost lists. You
can try (✎)
concat [ [ [1,2], [3,4] ]
, [ [5,6], [7,8] ]
]
18
Monads in Haskell 6.2
level list structure. N is the topmost level in the nested structure that is not effected by the
transformation.
Application of LA returns the outermost list with its elements replaced by the results of
applying A on them. You can observe this using (✎) the Haskell code
fmap concat [ [ [1,2], [3,4] ]
, [ [5,6], [7,8] ]
]
The monad property (F ) = (F ) can be observed by applying concat to the results of the
two expressions given above. Both yield the same result.
Now, consider the transformation : IC ! L, which, parametrised with an object A, maps a mem-
ber from A to the singleton list in LA containing that member. Again, we face two alternatives:
LA applies the list making to the whole input list, returning a list which simply contains the
original input list as sole member. Feed (✎) the code
(nx -> [x]) [1,2,3]
to Haskell.
LA applies the list making to each element in the input list, returning the input list with its
members replaced by the according singleton lists. You can type (✎)
fmap (nx -> [x]) [1,2,3]
to verify this.
The monad property (L ) = idL = (L) can be observed (✎) by applying concat to the
results of the two expressions given above. Both yield the same result.
6.2.1 Lemma Haskell’s List structure L, together with concatenation and the creation of single-
ton lists , forms a monad (L; ; ).
6.2.2 Proof We already know that L is a functor (Lemma 4.3.1). The remaining work to do is:
Prove the naturality of and , and show that the monad property holds.
We use the Haskell notation for lists, i.e., [ ] denotes the empty list, and x : x0 denotes the list x0
prepended with the element x. The definition of fmap yields
(Lf )[ ] = [ ]
(Lf )(x : x0 ) = x : (Lf )x0 .
Note, that concat is defined in terms of foldr and a binary concatenation operator (+
+) we
consider primitive:
concat = foldr (++) [] .
8f :A!B B L2 f = Lf A
holds. We use induction on the structure of the list x.
Let x = [ ] 2 L2 A. Then, we observe that
19
CHAPTER 6. MONADS
8f :A!B B f = Lf A
holds. The proof reads
(B f )x
=
B (f x)
=
[f x]
=
(Lf )[x]
=
(Lf )(A x)
=
(Lf A )x .
.
Part III Now we show that the monad properties for lists hold.
Again, we use induction on the structure of a list x.
(A LA )[ ]
=
A ((LA )[ ])
=
A [ ]
=
A (LA [ ])
=
(A LA )[ ] .
20
An alternative definition of the monad 6.3
+ LA y 0 )
A (y +
=
A (LA (y : y 0 ))
=
(A LA )x .
(A LA )[ ]
=
A ((LA )[ ])
= definition of fmap
A [ ]
= definition of concat
[]
= definition of concat
A [[ ]]
=
A (LA [ ])
=
(A LA )[ ]
Let x = y : y 0 , where y 2 A and y0 2 LA.
(A LA )x
=
A ((LA )(y : y 0 ))
= definition of fmap
A (A y : (LA )y 0 )
= definition of concat
+ A ((LA )y 0 )
A y +
= induction: A LA = idLA
+ y0
A y +
=
y : y0
=
A [y : y 0 ]
=
A (LA (y : y 0 ))
=
(A LA )x .
6.3.1 Definition Given (F; ; ), we define the binary infix operator bind by
! : FA (A ! F B) ! FB
x ; f 7! (B F f )x .
Note, that we assume x to be a member of F A, which restricts us to categories which support this
— like, e.g., the category of sets, or H. In literature not related to the Haskell language a point-free
variant of the bind operator, called Kleisli star, is used:
21
CHAPTER 6. MONADS
6.3.2 Definition The unary postfix operator Kleisli star, a point-free version of the bind operator,
is defined by
: (A ! F B) ! (F A ! F B)
f 7! B Ff .
Obviously 8 x 2 F A ; f : A ! F B x ! f = f x.
In fact, the bind operator is a special case of the Kleisli star, restricted to categories where the
objects have members. Ignoring the lack of elegance and generality, we stick to the bind notation
in the following, since this matches the Haskell notation. However, it is easy (✎) to reformulate
the following statements and proofs to the more general Kleisli notation.
The second argument of ! is a function f , which adds some structure F to what it returns. In-
tuitively, as defined above, the bind operator lifts the passed function f , applies it to the object
x 2 F A, and then applies to remove one level of structure nesting.
The bind operator is used to formulate the three monad laws. We give them in the form of a lemma
and prove their correctness using the monad properties given in Definition 6.1.1.
Just as a hint for the exercise: Using Kleisli notation, the three monad laws read
1. 8 f : A ! F B f A = f
2. 8 A 2 O A = idF A
3. 8 f : A ! FB ; g : B ! FC g f = (g f )
6.3.4 Proof of Lemma 6.3.3.
A x ! f
= bind operator, Definition 6.3.1
(B F f )(A x)
=
(B F f A )x
= naturality of means F B If = F f A
(B F B If )x
= using the monad property (F ) = idF
(idF B f )x
=
fx
. Part II Let x 2 F A.
x ! A
= using Definition 6.3.1 of the bind operator
(A F A )x
= monad property (F ) = idF
22
An alternative definition of the monad 6.3
idF A x
=
x
! : FO A (A ! FO B ) ! FO B .
Then, the triple (FO ; ; ! ) forms a monad, iff the three monad laws (as given in Lemma 6.3.3)
hold.
Note, that is not required to be a natural transformation. This turns out to be a result of the three
monad laws.
6.3.6 Definition In the situation of Theorem 6.3.5, we can define and FM for all A 2 OC and all
f : A ! B by
C
A : 2A
FO ! FO A
x 7! x ! idFO A
23
CHAPTER 6. MONADS
and
FM f : FO A ! FO B
x 7! x! B f .
Again, we write F for FO as well as for FM , since they hardly can be confused. Definition 6.3.6 is
used in the following, so bear in mind that we have to prove the monad properties of the (F; ; )
tuple just defined, by using the three monad laws we claimed to hold in Theorem 6.3.5.
6.3.7 Proof of Theorem 6.3.5. Due to Lemma 6.3.3, we only need to show the functor properties
of F , that is a natural transformation, and that we can conclude the monad properties given
in Definition 6.1.1 from the three monad laws given in Lemma 6.3.3.
8f :A!B Ff : FA ! FB
holds, follows directly from its definition.
(F g F f )x
= Definition 6.3.6 of FM applied twice
(x !B f ) ! C g
= the 3rd monad law
x ! (y 7! (B f )y ! C g)
= the 1st monad law
x ! (y 7! (C g f )y)
= removing the -expression
x ! C g f
= Definition 6.3.6 of FM applied to (g f )
(F (g f ))x .
Thus, 8 f : A ! B ; g : B !C F (g f ) = F f Gf .
Together, we conclude that F is an endofunctor in C .
(F f A )x
=
F f (A x)
= Definition 6.3.6 of FM
A x ! B f
= the 1st monad law
(B f )x
= inserting the identity functor
(B If )x
24
An alternative definition of the monad 6.3
Thus, : IC !
_ F.
.Part III To show (F ) = idF = (F ), we prove the two equations separately for fixed
arbitrary A 2 O, and x 2 F A.
. Case III.a For the left hand side equation the proof reads
(A F A)x
=
A((F A)x)
= Definition 6.3.6 of
((F A)x)
! idF A
Use that (A0 )x ! f = f x for x 2 A0 , f : A0 ! F B 0 , and assign A0 := F A,
=
B 0 := A and f := idF A .
idF A x
=
x .
(A F A)x
=
(A)((F A)x)
h
= Definition 6.3.6 of FM with f := A reads (F A )y =y ! F A A , for y 2 F A.
(A)(x
! F A A)
= Definition 6.3.6 of
(x !F A A) ! idF A
= the 3rd monad law
x ! (y 7! (F A A)y ! idF A )
= Definition 6.3.6 of used backwards
x ! (y 7! A((F A A)y))
= remove the -expression
x ! A
F A A
= Part III.a: (F ) = idF
x ! A
= the 2nd monad law
x
(A F A)x
=
(A)((F A )x)
= Definition 6.3.6 of , using F A instead of A there
(A)(x
! idF 2 A )
= definition of again
(x !idF 2 A ) ! idF A
= the 3rd monad law
x ! (y 7! idF 2 A y ! idF A )
=
x ! (y 7! y ! idF A )
= definition of
x ! (y 7! (A)y)
= removing the -expression
x ! A
=
25
CHAPTER 6. MONADS
x ! id F A A
= Part III.a: (F ) = idF
x ! A
F A A
= building a -expression
x ! (y 7! (A)((F A A)y))
= definition of
x ! (y 7! (F A A)y ! idF A )
= the 3rd monad law
(x !F A A) ! idF A
= definition of
(A)(x
! F A A)
= definition of FM , used backwards, with f := A
(A)((F A)x)
=
(A F A)x
In fact, there are two more functions defined —namely (>>) and fail— but we do not discuss
them here. The return function is Haskell’s equivalent to the natural transformation . The op-
erator >>= simply is the bind operator. The type variable m is bound to a unary type constructor:
the functor.
As for the Functor class, being a member of Monad alone does not assure adherence to the
monad laws. This has to be checked by the programmer in advance. Luckily, Theorem 6.3.5 saves
us from doing this again, if we did it before.
Definition 6.3.1 directly shows how we could make Haskell’s List structure a member of the
Monad class — if it was not already:
It is a nice —and easy— exercise, to verify the three monad laws for the Maybe monad as given
above.
Membership of the Monad class allows usage of the do-notation. This is a syntactic construct pro-
vided by the Haskell compiler which resembles imperative programming.
6.4.1 Definition The do-notation is defined (Section 3.14 of [5]) using the following recursive set
of rules (for brevity, we skip the definition of let clauses). To form a do-expression, the do
26
First step towards IO 6.5
(String,String)
where the former string models the characters waiting in the input stream, and the latter repre-
sents what was written to the output stream. Hence, a value of
("foo","bar")
means, that the output bar has been written already, and that foo has not been read from the
input yet.
Obviously, we can model IO by passing around a parameter containing the state of the world out-
side our Haskell program. In fact, this is what we’ll do, however encapsulating the state in a
monad structure.
Functions that change the state of the world, i.e., read or write the streams, are called state transfor-
mations. We use (✎) a data type
to model them. The state transformations are equipped with a type parameter a. This is required,
because we want the state transformations to return data, depending on the change performed
to the world. E.g., reading a character from the input not only changes the input stream (by
removing that character), but also returns that character. Hence, we refer to a as return value of a
transformation.
With this, we can already define (✎) our primitive input and output routines:
getc :: ST Char
getc = ST( ((i:is),os) -> ((is,os),i) )
27
CHAPTER 6. MONADS
The getc function takes the first character from the input stream, and returns it in i — note the
type parameter Char passed to ST. Clearly, getc transforms the input state, by removing one
character from the input stream. The output stream remains unchanged.
Writing to the output stream appends the character passed to the putc function to the already
written output os. We don’t want to return anything, which we model by returning Haskell’s
unit type value ().
So how do we define the monad? Since we want to use do-notation, we make the state transfor-
mation an instance of the monad class
The natural transformation is used to return a value, i.e., A maps a value x 2 A to a state
transformation which returns x without altering the state of the world s0 :
A x := s0 7! (s0 ; x) .
return :: a -> ST a
return val = ST (s0 -> (s0,val)) .
The bind operator is a bit more complex: Assuming S to be the functor that embodies the structure
ST of a state transformation, for a morphism f : A ! SB and a transformation t0 2 SA (which
returns a value in A) we define
t0 ! f := s0 7! t1 s1 where ( s1 ; v ) = t 0 s0 and t1 = fv .
To understand this, observe that the binding of a state transformation t0 to a function f : A ! SB
returns a new state transformation, which is assembled as follows:
The state transformation t0 is applied to the input state s0 of the generated transformation, re-
turning a new state s1 and a return value v . Applying f to this value leads to a new state trans-
formation t1 which is then applied to the new state s1 .
It becomes clear now, how the return value is used: Transforming the input state s0 using t0
returns a value in v which is passed to f to create the new state transformation t1 .
In Haskell syntax, the bind operator reads as follows:
6.5.2 Notation We introduce a bit of notation to increase readability of the following proof: Let
[q ]k address the k -th member of a tuple q if it contains at least k entries, i.e.,
8 k; n 2 N ; 1 k n [(a1 ; : : : an )]k = ak .
We use this notation to access the new state and the return value of a state transformation. The
definition of the ! operator for the ST structure now reads
t!f
=
s 7! ( f [ts]2 ) [ts]1
where [ts]1 refers to the new state, and [ts]2 refers to the return value.
28
First step towards IO 6.5
. Part I The first two parts are quite trivial. But let us get used to the []k -notation. We see the
first monad law by
t! A
=
s 7! (A [ts]2 )[ts]1
= definition of
s 7 (s0 7! (s0 ; [ts]2 ))[ts]1
!
=
s 7! ([ts]1 ; [ts]2 )
=
s 7! ts
=
t .
A x ! f
= definition of ! using Notation 6.5.2
s 7! (f [(A x)s]2 )[(A x)s]1
= definition of
s 7! (f [(s0 7! (s0 ; x))s]2 )[(s0 7! (s0 ; x))s]1
=
s 7! (f [(s; x)]2 )[(s; x)]1
=
s 7! (f x)s
=
fx .
. Part III The least intuitive part is probably the third monad law:
(t ! f ) ! g = t ! (y 7! f y ! g)
( definition of ! applied to its right occurrence on the left hand side
s7! (g[(t ! f )s]2 )[(t ! f )s]1 = t ! (y 7! f y ! g)
( definition of ! applied to its left occurrence on the right hand side
s 7! (g [(t ! f )s]2 )[t ! f )s]1 = s 7! (f [ts]2 ! g )[ts]1
( generalising over all s
(g [(t ! f )s]2 )[(t ! f )s]1 = (f [ts]2 ! g )[ts]1
( definition of ! applied on the right hand side
(g [(t ! f )s]2 )[(t ! f )s]1 = (s2 7! (g [(f [ts]2 )s2 ]2 )[(f [ts]2 )s2 ]1 )[ts]1
( resolving the -expression on the right hand side
(g [(t ! f )s]2 )[(t ! f )s]1 = (g [(f [ts]2 )[ts]1 ]2 )[(f [ts]2 )[ts]1 ]1
( observing the (g [X ]2 )[Y ]1 skeleton on both sides
(t ! f )s = (f [ts]2 )[ts]1 .
Now let us have a look at some programs written in the imperative programming style offered
by the do-notation. To discuss the code, we use imperative style language, like “program” or
“assign”.
29
CHAPTER 6. MONADS
is the shortest program simulating IO we can write so far. It does nothing, however we have
to understand this skeleton, which is also used below. The auxiliary functions getVal and
initialise are required in our model:
Since we only model IO operations —i.e., our program does not do real IO yet— we somehow
need to get an initial state of the input and output streams. This is done by
initialise :: ST ()
initialise = ST( -> (("foo",""),()))
which sets the input stream to "foo", and the output stream to be empty. This “setting the
state” is performed by creating a transformation, which ignores its input (hence -> ...) and
always returns the same state. Since initialisation shall not return a value, we return ().
When our program in do-notation has been executed, we access the state of the program via
which passes some arbitrary state undefined to the transformation returned by the do-expres-
sion.
So evaluation (using for “reduces to”) of t0 reads
t0
getVal do f initialise g
getVal initialise
getVal ( 7! (( "foo" ; "" ); ()))
( 7! (( "foo" ; "" ); ())) undefined
(( "foo" ; "" ); ())
✎ Second example Now we start to fill the skeleton given above. Let us just output one char-
acter:
After initialisation, this program writes c to the output stream. Setting t := initialise and
f := putc ’c’ , the evaluation of the program body (i.e., the do-notation) reads
do f t; f g
t ! ( 7! do f f g)
t ! ( 7! f )
definition of ! using Notation 6.5.2
s 7 (( 7! f )[ts]2 )[ts]1
!
s 7! f [ts]1
talways maps to the initial state (( "foo" , "" ),())
s 7! f ( "foo" ; "" )
f = putc ’c’
s 7! (( "foo" ; "c" ); ())
So, the do-notation returns a transformation that transforms an arbitrary initial state s into the
state ( "foo" ; "c" ) and the return value (). The getVal function binds s to undefined, and
the state of the world after evaluation of the program is returned.
30
The IO monad 6.6
✎ Third example This program prints the first input character to the output stream
Again, setting t := initialise , f := getc , g := putc and s0 := (( "foo" ; "" ); ()), the
evaluation of the do-expression reads
do f t; x f ; gx g
t ! ( 7! f ! (x 7! gx))
t ! ( 7! f ! g )
s 7! (( 7! f ! g )[ts]2 )[ts]1
since the -expression ignores its argument
s 7! (f ! g))[ts]1
using the definition of t which also ignores its input s
7! (f ! g) s0
7! ( s 7! (g[f s]2 )[f s]1 ) s0
7 (g[f s0 ]2 )[f s0 ]1
!
using f s0 = (( "oo" ; "" ); ’f’ )
7 (g ’f’ ) ( "oo" ; "" )
!
7! (( "oo" ; "f" ); ()) .
✎ Fourth example Since the do-notation works on a list of transformations, and also returns a
transformation, we can nest them. This leads to the ability to model subroutines in an imperative
style.
We define a (recursive) function that prints a string:
It should be clear how these functions are evaluated from the examples above. Therefore we do
not go through this now.
31
CHAPTER 6. MONADS
The big advantage is, that the monad structure may be some kind of “one-way” object. Neither
Definition 6.1.1, nor the definition given in Theorem 6.3.5, require a method to get things back out
of the monad. In the last section, we used a function getVal to reveal the innards of the monad,
but none of the laws and theorems mentioned above guarantees the existence of such a function.
The same applies to the function initialise.
Recall, that we accepted the structure of objects in a category to be unknown (Definition 3.1.1). If,
for example, we define a monad with a functor M , and apply M to an object A with well known
structure in our category, we end up with another object M A whose structure might be unknown.
And this is all there is about the IO monad. The Haskell programmer has no clue about its internal
structure, and it is not possible to access the state of the world. This protects one from doing funny
things, like, e.g., make a copy of the state of the world and transform both “versions” of the world
in different ways.
You may have noticed the similarity between the functions
getc :: ST Char
putc :: Char -> ST ()
puts :: String -> ST ()
getChar :: IO Char
putChar :: Char -> IO ()
putStr :: String -> IO ()
provided by Haskell.
The main difference is that we do not know the internal structure of the IO monad. All we know
is how to pass an object in that monad (M A) to a morphism (f : A ! M B ) using the bind operator,
thereby applying f to the “value” of the passed object.
32
Final remarks
This guide explored the theoretical backgrounds of Haskell’s IO monad. On the way we have
seen functors, natural transformations, and finally monads. All these purely theoretic concepts
appeared to have a quite practical correspondence in the Haskell programming language, as em-
phasized by the according examples.
It should be clear now, how the IO monad is used to pass around the state of the world, without
allowing the programmer to access it “too much”.
Recall the novice Haskell programmer mentioned in the introduction:
“I’ll just wrap the getLine in another function which only returns what I really need, maybe
convert the users’ input to an Int and return that.”
Now we can explain why the IO “thing” will always stick to a function that is somehow involved
in doing IO. But with the knowledge about the bind operator, we can also talk “monad-free”
functions into working on data returned from IO.
Also, we know why IO () denotes the type of a “void IO procedure”, and how the variable
assignment notation x<-... is translated into abstraction.
33
Bibliography
[1] Maarten Fokkinga. A Gentle Introduction to Category Theory — the calculational approach. In
Lecture Notes of the STOP 1992 Summerschool on Constructive Algorithmics, Part I, pages 1–72.
University of Utrecht, Netherlands, September 1992.
[2] Michael Barr, Charles Wells. Category Theory Lecture Notes for ESSLLI. In Lecture Notes for
ESSLLI’99, 1999. http://www.let.uu.nl/esslli/Courses/barr/barrwells.ps.
[3] Jeff Newbern. All About Monads. http://www.nomaware.com/monads/html/index.
html.
[4] Theodore Norvell. Monads for the Working Haskell Programmer — a short tutorial. Memo-
rial University of Newfoundland. http://www.engr.mun.ca/˜theo/Misc/haskell_
and_monads.htm.
[5] Simon Peyton Jones, et. al. Haskell 98 Language and Libraries — The Revised Report. December
2002. http://haskell.org/definition/.
34