0% found this document useful (0 votes)
66 views

Haskell Programmer's Guide IO Monad: The To The

This document provides an introduction to the IO monad in Haskell. It begins by discussing categories, functors, and natural transformations from a theoretical perspective using mathematical notation. It then shows how these concepts relate to and are implemented in Haskell. The document explains that the IO monad allows doing input/output in Haskell while maintaining referential transparency and purity. It does this by wrapping non-pure IO actions in the IO monad, which separates the pure functional world from effects.

Uploaded by

rmxuser
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
66 views

Haskell Programmer's Guide IO Monad: The To The

This document provides an introduction to the IO monad in Haskell. It begins by discussing categories, functors, and natural transformations from a theoretical perspective using mathematical notation. It then shows how these concepts relate to and are implemented in Haskell. The document explains that the IO monad allows doing input/output in Haskell while maintaining referential transparency and purity. It does this by wrapping non-pure IO actions in the IO monad, which separates the pure functional world from effects.

Uploaded by

rmxuser
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 34

The

Haskell Programmer’s Guide


to the
IO Monad

— 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]

PDF available at http://stefan-klinger.de.

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.

Version of 2005-Dec-15 15:39:54 .


Preface

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.

1.2 Related work


This guide tries to fit exactly between the available theoretical literature about category theory on
the one side (e.g., [1], [2]) and literature about how to program with monads ([3], [4]) on the other.
However, the sheer amount of available literature on both sides of this report dooms any approach
to offer a complete list of related work to failure.

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.

5. A -expression binding the free occurrences of y in expression e is written


(y 7! e) .
As a Haskell programmer, you should be familiar with -expressions.

6. Unless stated otherwise, most programming code is Haskell-pseudocode. In contradiction to


this, Haskell-code that should be tried out by the user is referred to with a ✎-sign.

6
Chapter 3

Categories

3.1 Categories in theory


Introductory example Let us start with a quite concrete example of a category: Sets (later called
objects) together with all the total functions on them (called morphisms):

 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.

3.1.1 Definition A category C = (OC ; MC ; TC ; C ; IdC ) is a structure consisting of morphisms MC ,


objects OC , a composition C of morphisms, and type information TC of the morphisms, which obey
to the following constraints.
Note, that we often omit the subscription with C if the category is clear from the context.
1. We assume the collection of objects O to be a set. Quite often the objects themselves
also are sets, however, category theory makes no assumption about that, and provides no
means to explore their structure.
2. The collection of morphisms M is also assumed to be a set in this guide.
3. The ternary relation T  M  O  O is called the type information of C . We want every
morphism to have a type, so a category requires
8 f 2 M 9 A; B 2 O (f; A; B ) 2 T .
We write f : A ! B for (f; A; B ) 2 TC . Also, we want the types to be unique, leading to
C
the claim
f :A ! B ^ f : A0 ! B 0 ) A = A0 ^ B = B 0 .
This gives a notion of having a morphism to “map from one object to another”. The
uniqueness entitles us to give names to the objects involved in a morphism. For f : A ! B
we call src f := A the source, and tgt f := B the target of f .
4. The partial function  : M  M ! M, written as a binary infix operator, is called the
composition of C . Alternative notations are f ; g := gf := g  f .

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].

3.2 Spot a category in Haskell


This guide looks at one particular category that can be recognised in the Haskell programming
language. There might be others of more or less interest, but for the purpose of explaining
Haskell’s Monad structure this narrow perspective is sufficient. For a more thorough discussion
about functional programming languages and category theory you might read [2].
With the last section in mind, where would you look for “the obvious” category? It is not required
that it models the whole Haskell language, instead it is enough to point at the things in Haskell
that behave like the objects and morphisms of a category.
We call our Haskell category H and use Haskell’s types —primitive as well as constructed— as
the objects OH of the category. Then, unary Haskell functions correspond to the morphisms MH ,
with function signatures of unary functions corresponding to the type information TH .
f :: A -> B corresponds to f : A !B
H
Haskell’s function composition ‘.’ corresponds to the composition of morphisms H . The identity
in Haskell is typed

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 Functors in theory


One can define mappings between categories. If they “behave well”, i.e., preserve the structural
properties of being an object, morphism, identity, the types and composition, they are called
functors.

4.1.1 Definition Let A; B be categories. Then two mappings


FO : OA ! OB and FM : MA ! MB
together form a functor F from A to B , written F : A ! B, iff
1. they preserve type information, i.e.,
8 f : A A! B F M f : FO A ! FO B
B
,

2. FM maps identities to identities, i.e.,


8 A 2 OA FM idA = idFO A ,
3. and application of FM distributes under composition of morphisms, i.e.,
8 f : A A! B ; g : B A! C 
FM (g A f ) = FM g B FM f  .

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.

4.1.3 Definition Let A be a category. A functor F : A ! A is called endofunctor.


It is easy to define a “identity” on a category, by simply combining the identities on objects and
morphisms the same way we just have combined the functors. Also, applying one functor after
another —where the target category of the first must be the source category of the second— looks
like composition:

4.1.4 Definition Let A; B ; C be categories, F : A ! B and G : B ! C . We define identities


IA := idOA ]MA

and functor composition GF with


8A2O (GF )A := G(F A)
8f 2M (GF )f := G(F f ) .

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 .

4.1.6 Proof is easy (✎). 


With that in mind, one can even consider categories to be the objects, and functors to be the
morphisms of some higher level category (a category of categories). Although we do not follow
this path, we do use composition of functors and identity functors in the sequel. However, we
use the suggestive notation GF instead of G  F since we do not discuss category theory by its
own means.

4.2 Functors implement structure


The objects of a category may not have any structure, the structure may not be known to us, or
the structure may not be suitable for our programming task, just as characters without additional
structure are not suitable to represent a string. Functors are the tool to add some structure to an
object.
A functor L may implement the List structure by mapping the object “integers” to the object “list
of integers”, and a function f that operates on integers, to a function Lf which applies f to each
element of the list, returning the list of results. The same —suitably defined— functor L would
map any object in the category to the corresponding list object, like the object “characters” to the
object “strings”, and booleans to bit-vectors.
In this example, a (not the) suitable category S for L : S ! S would be the category where the ob-
jects are all sets, and the morphisms are all total functions between sets — that is our introductory
example. Note, that the structure of the integers themselves is not affected by the functor appli-
cation, hence we can say that the structure is added on the outside of the object. The addition of
structure by application of a functor is often referred to using the term lifting, like in “the integers
are lifted to lists of integers” or “the lifted function now operates on lists.”
This vague explanation of how a functor describes a structure is refined during the remainder of
this guide. But already at this point, you can try to think about endofunctors that manifest struc-
tures like pairs, n-tuples, sets, and so on (✎ define some of these). Obviously, functor composition
implements the nesting of structures, leading to structures like “list of pairs”, “pair of lists”, etc.

4.3 Functors in Haskell


Following our idea of a Haskell category H, we can define an endofunctor F : H ! H by giving
a unary type constructor F, and a function fmap, constituting FO and FM respectively.
The type constructor F is used to construct new types from existing ones. This action corresponds
to mapping objects to objects in the H category. The definition of F shows how a functor imple-
ments the structure of the constructed type. The function fmap lifts each function with a signature
f : A ! B to a function with signature F f : F A ! F B .

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

class Functor f where


fmap :: (a -> b) -> f a -> f b

which allows overloading of fmap for each functor.

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.

--loading ’Char’ seems to be required in GHCi, but not in Hugs


:m Char

: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

fmap :: (a -> b) -> Maybe a -> Maybe b


fmap f Nothing = Nothing
fmap f (Just x) = Just (f x)

while fmap for the List structure can be written as

fmap :: (a -> b) -> [a] -> [b]


fmap f [] = []
fmap f (x:xs) = (f x):(fmap f xs)

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:

1. f : A ! B ) F f : F A ! F B is fulfilled, since being a member of the Functor class implies


that a function fmap with the according signature is defined.
2. 8A2O F idA = idF A translates to
fmap id == id

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

4.3.1 Lemma Maybe and List are both 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) ,

and the third property, F (g  f ) = F g  F f , by


(fmap g . fmap h) Nothing
== fmap g (fmap h Nothing)
== fmap g Nothing
== Nothing
== fmap (g . h) Nothing ,

(fmap g . fmap h) (Just y)


== fmap g (fmap h (Just y))
== fmap g (Just (h y))
== Just (g (h y))
== Just ((g . h) y)
== fmap (g . h) (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) ,

and the third property, F (g  f ) = F g  F f can be observed in


fmap (g . f) []
== []
== fmap g []
== fmap g (fmap f [])
== (fmap g . fmap f) []

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

5.1 Natural transformations in theory


A natural transformation is intended to transform from one structure to another, without effect-
ing, or being influenced by, the objects in the structure.
Imagine two functors F and G —imposing two structures— between the same categories A and
B. Then a natural transformation is a collection of morphisms in the target category B of both
functors, always mapping from an object F A to an object GA. In fact, for each object A in the
source category there is one such morphism in the target category:

5.1.1 Definition Let A; B be categories, F; G : A ! B. Then, a function


 : OA ! MB
A 7 ! A
is called a transformation from F to G, iff
8 A 2 OA A : F A ! GA
B
,

and it is called a natural transformation, denoted  : F !


_ G, iff

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

5.2 Natural transformations in Haskell


First note, that a unary function polymorphic in the same type on source and target side, in fact
is a transformation. For example, Haskell’s Just is typed

Just :: forall a. a -> Maybe a .

If we imagine this to be a mapping Just : OH ! MH , the application on an object A 2 OH means


binding the type variable a to some Haskell type A. That is, Just maps an object A to a morphism
of type A ! Maybe A.

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.1 Lemma 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

maybeToList :: forall a. Maybe a -> [a]


maybeToList Nothing = []
maybeToList (Just x) = [x]

listToMaybe :: forall a. [a] -> Maybe a


listToMaybe [] = Nothing
listToMaybe (x:xs) = Just x .

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).

5.3 Composing transformations and functors


To describe the Monad structure later on, we need to compose natural transformations with func-
tors and with other natural transformations. This is discussed in the remainder of this chapter.

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:

5.3.1 Definition Let A; B ; C ; D be categories, E : A ! B , F; G : B ! C , and H : C ! D. Then, for


a natural transformation  : F ! _ G, we define the transformations E and H with

(E )A := EA and (H )B := H (B ) ,


where A varies over OA , and B varies over OB .

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

hold. In (other) words, H and E are both natural transformations.

14
Composing transformations and functors 5.3

5.3.3 Proof of Lemma 5.3.2

. 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.

5.3.4 Definition Let A; B be categories, F; G; H : A ! B and  : F !


_ G,  : G !
_ H . Then we
define the transformations
idF : OA ! MB and  : OA ! MB
A 7 ! idF A A 7 ! A  A .

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

5.3.5 Lemma In the situation of Definition 5.3.4,


idF : F !
_ F ,
 : F !
_ H ,
idG  =  =  idF

hold.

5.3.6 Proof of Lemma 5.3.5.


The naturality of idF is trivial (✎). For the naturality of  consider any morphism f : A ! B.
A
Then,
:G !
_ H ^ :F !
_ G
)
Hf A = B  Gf ^ Gf  A = B  F f
) apply A on the left, B on the right
(Hf  A)  A = (B  Gf )  A

^ B  (Gf  A) = B  (B  F f )
) the two middle terms are equal
Hf (A  A) = (B  B )  F f
) Definition 5.3.4
Hf  A = B  F f
)
 : F !_ H

16
Chapter 6

Monads

6.1 Monads in theory


6.1.1 Definition Let C be a category, and F : C ! C. Consider two natural transformations
 : IC !
_ F and  : F 2 !_ F.

The triple (F; ; ) is called a monad, iff


(F ) = (F ) and (F  ) = idF = (F ) .

(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.

6.2 Monads in Haskell


The list structure (we use L to denote the according functor) provided by the Haskell language is
probably the most intuitive example to explain a monad. That it is a monad indeed is stated in
Lemma 6.2.1.
Imagine a list of lists of lists of natural numbers (i.e., L3 N). With a transformation like list con-
catenation ( : L2 ! L) we can choose between two alternatives of flattening, considering L3 to
be a “list of (list of lists)” or a “(list of lists) of lists”:

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] ]
]

in your Haskell interpreter.


LA lifts the concatenation of a list of lists into the outermost list, applying it to each of the second-
level lists of lists. Here, A refers to the object N, corresponding to the members of the third

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] ]
]

with your interpreter.

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 (++) [] .

From this, we conclude B (x : x0 ) = x +


+ B x0 and B [ ] = [ ].

. Part I To prove that  is a natural transformation, we have to show that

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

(B  L2 f )[ ] = (Lf  A)[ ]


holds by looking at the definitions of fmap (L) and concat (A and B ).

19
CHAPTER 6. MONADS

Let x = y : y 0 , where y 2 LA and y0 2 L2 A.


(B  L2 f )x
=
B ((L2 f )(y : y 0 ))

= definition of fmap
B ((Lf )y : (L2 f )y 0 )

= definition of concat
+ B ((L2 f )y 0 )
(Lf )y +

= induction: (B  L2 f )y0 = (Lf  A )y0
+ (Lf )(A y 0 )
(Lf )y +
=
+ A y 0 )
(Lf )(y +

= definition concat
(Lf )(A (y : y 0 ))
=
(Lf  A )x
. Part II To prove that  is a natural transformation, we have to show that

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.

. Part III.a Proof that (L) = (L).


Let x = [ ] 2 F 3 A.

(A  LA )[ ]
=
A ((LA )[ ])
=
A [ ]
=
A (LA [ ])
=
(A  LA )[ ] .

Let x = y : y 0 , where y 2 L2 A and y0 2 L3 A.


(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 y 0 )
A y +

= definition of concat

20
An alternative definition of the monad 6.3

+ LA y 0 )
A (y +
=
A (LA (y : y 0 ))
=
(A  LA )x .

. Part III.b Proof that (L ) = idL = (L).


Let x = [ ] 2 LA.

(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 An alternative definition of the monad


While the previous definition of the monad structure is quite intuitive, there is another one and,
in fact, that is the one Haskell uses for its Monad class.
First, we use the monad as introduced in Definition 6.1.1, to define a bind operator, and we prove
the three monad laws to hold for this definition. Then, we show that the three monad laws alone
imply all the properties required to form a monad, hence yield an alternative definition.

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.

6.3.3 Lemma The three monad laws are:


1. A resembles a left identity with respect to the bind operator:
8 f : A ! F B ; x 2 A A x ! f = f x .
2. A is a right identity with respect to the bind operator:
8 x 2 FA x ! A = x
3. The bind operator is quite close to being associative:
8 x 2 FA; f : A ! FB ; g : B ! FC
(x ! f ) ! g = x ! (y 7! f y ! g )

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.

. Part I Let f : A ! F B , and x 2 A.

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

. Part III Let x 2 F A, f : A ! F B , and g : B ! F C.


(x !f ) ! g
= using Definition 6.3.1 of the bind operator
((B

 F f )x) ! g
= dito
(C  F g)((B  F f )x)
=
(C  F g  B  F f )x

= naturality of  means F C  F 2 g = F g  B
(C  F C  F 2 g  F f )x
= using the monad property (F ) = (F )
(C  F C  F 2 g  F f )x
= F is a functor, so it distributes under composition.
(C  F (C  F g  f ))x
= the bind operator again
x ! C  F g  f
= build a -expression
x ! (y 7! (C  F g  f )y)
=
x ! (y 7! (C  F g )(f y ))

= the bind operator again
x ! (y 7! f y ! g)

Note, that the three monad laws do not make use of  or FM . In the following, it turns out that
defining (F; ; ) is equivalent to defining (FO ; ; ! ), i.e., each of these tuples can be defined in
terms of the other: The one direction is obvious using Definition 6.3.1, the reverse is given in
Definition 6.3.6. Moreover, Theorem 6.3.5 states that the three monad laws are another way to
formulate the previously given monad properties (Definition 6.1.1).

6.3.5 Theorem Let C be a category, and FO : O ! O an arbitrary object mapping. Consider a


function (like a transformation)
 : O ! M
A 7 ! A
with A : A ! F A, and an operator
C

! : 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.

. Part I First, we show the functor properties of F .

. Part I.a That FM preserves type information, i.e., that

8f :A!B Ff : FA ! FB
holds, follows directly from its definition.

. Part I.b FM preserves identities, since for all x 2 F A

(F idA )x = x ! A  idA = x ! A = x = idF A x


holds.

. Part I.c For the distribution of FM under composition we fix arbitrary x 2 F A, f : A ! B


and g : B ! C . Then

(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 .

.Part II Since F is a functor,  is a transformation from IC to F by definition. To show its


naturality, choose arbitrary x 2 A and f : A ! B . Then

(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 .

. Case III.b For the right hand side equation we calculate

(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

. Part IV To show (F ) = (F ) we fix arbitrary A 2 O and x 2 F 3 A. Then,

(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


6.4 Haskell’s monad class and do-notation


Haskell provides the programmer with a class Monad, which uses the alternative definition given
above.

class Monad m :: (* -> *) where


(>>=) :: forall a b. m a -> (a -> m b) -> m b
return :: forall a. a -> m a;

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:

instance Monad List where


return x = [x]
x >>= f = (concat . fmap f) x

Also, we could make Maybe a member of the Monad class:

instance Monad Maybe where


return = Just
Nothing >>= f = Nothing
(Just x) >>= f = f x

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

keyword operates on a sequence of terms t0 ; : : : ; tn , where tn is a Haskell expression, and the ti


with 0  i < n are either Haskell expressions as well, or pattern matching expressions vi ei ,
where vi are patterns introducing new variable names.
1. If the list of terms contains only one term —which then must be an expression— the do is
simply removed:
do feg := e .
2. If the first term is a pattern matching expression v e, a -expression is created bind-
ing the pattern v in a do-expression containing the remaining terms. The result of the
expression e is bound to the new -expression:
do fv e; t 1 ; : : : ; t n g := e ! (v 7! do f t1 ; : : : ; tn g) .
Note, how this resembles variable assignment in an imperative programming language, and
also justifies the name of the bind operator.
3. If there are multiple terms in the sequence, but the first one does not contain a pattern
matching, we use
do f e; t 1 ; : : : ; t n g := e ! ( 7! do f t1 ; : : : ; tn g) ,
which means that the value of e is not used in the remaining term.
Above, the sequence of terms is surrounded by curly braces, but it is also possible to use
Haskell’s two-dimensional syntax.

6.5 First step towards IO


As introduction, we build a monad that allows us to model IO operations. Assume our running
program to be connected to an input and an output character stream. We encapsulate their state
inside a monad structure and use imperative programming style to manipulate them. Hence, in
the following example, we do not use the “real” IO monad.
The state of the input and output streams is maintained in a pair of strings

(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

data ST a = ST( (String,String) -> ((String,String),a) )

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) )

putc :: Char -> ST ()


putc c = ST( (is,os) -> ((is,os++[c]),()) )

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

instance Monad ST where


...

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) .

In Haskell syntax (✎), this reads

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:

(>>=) :: ST a -> (a -> ST b) -> ST b


(ST t0) >>= f = ST( s0 -> let (s1,val) = t0 s0
(ST t1) = f val
in t1 s1
)

6.5.1 Theorem The triple (ST,return,>>=) is a monad.

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

6.5.3 Proof that S forms a monad.

. 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 .

. Part II And the second law is shown by

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 .

This is the definition of the ! operator after applying the -expression.


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

✎ First example The code fragment

t0 = getVal (do initialise


)

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

getVal :: ST a -> ((String,String),a)


getVal (ST p) = (p undefined)

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:

t1 = getVal (do initialise


putc ’c’
)

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

t2 = getVal (do initialise


x <- getc
putc x
)

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:

puts :: String -> ST ()


puts "" = return ()
puts (x:xs) = do putc x
puts xs --recursion

and then use it in

t3 = getVal (do initialise


getc
x <- getc
puts "2nd char is " --call puts
putc x
)

It should be clear how these functions are evaluated from the examples above. Therefore we do
not go through this now.

6.6 The IO monad


A closer look at the definition of the ST monad reveals, that the pair of strings we used to model
input and output streams is not mentioned at all. In fact, the definition of ST is a well known
method to pass around state information between function calls.
One might argue that these achievements are vile, providing nothing more than syntactic sugar
that avoids augmentation of function signatures to explicitly pass a state.
However, this is not entirely true.

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 ()

defined above, and the standard IO functions

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

You might also like