Value Recursion in Monadic
Computations

Levent Erkök
M.S. Computer Science, 1998, The University of Texas at Austin
B.S. Computer Engineering, 1994, Middle East Technical University

A dissertation presented to the faculty of the
OGI School of Science and Engineering
at Oregon Health and Science University
in partial fulfillment of therequirements for the degree
Doctor of Philosophy
inComputer Science and Engineering

October 2002

c Copyright 2002 by Levent Erkök
All Rights Reservedii

The dissertation “Value Recursion in Monadic Computations” by Levent Erkök has
been examined and approved by the following Examination Committee:

Dr. John Launchbury
Professor
Thesis Research Advisor

Dr. Mark P. Jones
Associate Professor

Dr. Richard Kieburtz
Professor

Dr. David Maier
Professor

Dr. Ross Paterson
Lecturer, City University, Londoniii

Acknowledgements
I would like to thank my advisor, John Launchbury, for his guidance, help, and friendshipover the last four years. I would have never completed this thesis without him leading theway. John knew exactly when to intervene and when to set me free, providing just theright balance. I always felt privileged to be working with him. Thank you John.
Many people contributed to this work. Ross Paterson, Mark Jones, Dick Kieburtz,
and Dave Maier have all done an excellent job of providing me with invaluable feedback
and guidance. In particular, Ross took an early interest in our work, helping out withthe technical development over the years. Sava Krstić was a constant source of help,
answering my endless questions with great enthusiasm. Nick Benton, Andrzej Filinski,
and Amr Sabry answered many e-mail questions. I am grateful to Amr for constructively
challenging our work, making us understand it better. Andy Moran collaborated with
us in developing the semantics of value recursion for the IO monad. Jeff Lewis helped
me understand the internals of Hugs, and helped with the implementation of the mdonotation. Simon Peyton-Jones answered many questions over e-mail, and implemented the
mdo-notation in GHC. The PacSoft research group at OGI provided a fun and stimulating
environment at all times. I had many joyful conversations with Zine Benaissa, Magnus
Carlsson, Iavor Diatchki, John Matthews, Thomas Nordin, Emir Pašalić, Walid Taha, andmany other friends at OGI. My heartfelt thanks go to all of them.
I am indebted to numerous people for shaping up my thinking and expanding myhorizons over the years, including Sadi Yalgın, Halit Oğuztüzün, Cem Bozşahin, Hamilton
Richards, and Nicholas Asher. I thank them all.
My research was supported by grants from the Air Force Materiel Command (F1962896-C-0161) and the National Science Foundation (CCR-9970980). I am thankful to ouradministrative staff for taking care of all the necessary details with meticulous care.
I would like to thank my parents, Fatma and Atila Erkök, for always being supportive,
and trusting me with my decisions. I sincerely hope that I am worthy of their trust.
Finally, I would like to thank my wife Şengül Vurgun, for being on my side throughthick and thin, taking over many of my responsibilities, and especially for believing in me
when I did not. Thank you Şengül, I could not have done it without you.

iv

Contents
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
Contents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

v

Abstract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viii
1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Recursion and effects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 A motivating example: Modeling circuits using monads . . . . . . . . . . . 2
1.3 Recursive monadic bindings . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.4 A generic mfix ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5 The basic framework and notation . . . . . . . . . . . . . . . . . . . . . . . 10
1.6 Outline of the thesis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2 Properties of value recursion operators . . . . . . . . . . . . . . . . . . . .
2.1 Strictness (Nothing from nothing) . . . . . . . . . . . . . . . . . . . . . . .
2.2 Purity (Just like fix ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3 Left shrinking (No recursion – No fix ) . . . . . . . . . . . . . . . . . . . . .
2.4 Sliding (Pure mobility) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.5 Nesting (Two for the price of one) . . . . . . . . . . . . . . . . . . . . . . .
2.6 Derived properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.6.1 Constant functions . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.6.2 Approximation property . . . . . . . . . . . . . . . . . . . . . . . . .
2.6.3 Pure right shrinking . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.6.4 Parametricity: The “free” theorem . . . . . . . . . . . . . . . . . . .
2.7 Stronger properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.7.1 Strong sliding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.7.2 Right shrinking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.8 Classification and summary . . . . . . . . . . . . . . . . . . . . . . . . . . .

12
12
13
13
14
15
17
17
18
18
20
21
22
22
23

3 Structure of monads and value recursion . . . . . . . . . . . . . . . . . . .
3.1 Monads with a strict bind operator . . . . . . . . . . . . . . . . . . . . . . .
3.2 Idempotent monads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3 Commutative monads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4 Monads with addition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.5 Embeddings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.6 Monad transformers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

25
25
28
29
30
31
33

v

3.7

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

4 A catalog of value recursion operators . . . . . . . . . . . . . . . . . . . . .
4.1 Identity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Exceptions: The maybe monad . . . . . . . . . . . . . . . . . . . . . . . . .
4.3 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.4 State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.5 Output monad and monads based on monoids . . . . . . . . . . . . . . . . .
4.6 Environments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.7 Tree monad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.8 Fudgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.9 Monad transformers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.10 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

35
35
36
38
42
46
48
49
51
53
55

5 Continuations and value recursion . . . . . . . . . . . . . . . . . . . . . . . 58
5.1 A monad for continuations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
5.2 The continuation monad transformer . . . . . . . . . . . . . . . . . . . . . . 61
5.3 First-class continuations and value recursion . . . . . . . . . . . . . . . . . . 62
5.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
6 Traces and value recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.1 Parameterized value recursion . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.2 Preliminaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.2.1 Symmetric monoidal categories . . . . . . . . . . . . . . . . . . . . . 68
6.2.2 Traced symmetric monoidal categories . . . . . . . . . . . . . . . . . 69
6.2.3 Traces and Conway operators . . . . . . . . . . . . . . . . . . . . . . 70
6.3 Traces and value recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
6.3.1 Commutative monads and traces . . . . . . . . . . . . . . . . . . . . 72
6.3.2 Monads arising from commutative monoids . . . . . . . . . . . . . . 74
6.3.3 The correspondence . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.4 Dropping the monoidal requirement . . . . . . . . . . . . . . . . . . . . . . 78
6.4.1 Arrows and loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6.4.2 Traced premonoidal categories . . . . . . . . . . . . . . . . . . . . . 80
6.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7 A recursive do-notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7.2 The basic translation and design guidelines . . . . . . . . . . . . . . . . . . 86
7.2.1 Let generators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
7.2.2 Segmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
7.2.3 Shadowing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
7.3 Translation of mdo-expressions . . . . . . . . . . . . . . . . . . . . . . . . . 91
7.3.1 Preliminaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
7.3.2 The translation algorithm . . . . . . . . . . . . . . . . . . . . . . . . 92
7.3.3 Type checking mdo-expressions . . . . . . . . . . . . . . . . . . . . . 95
vi

7.4
7.5

Current status and related work . . . . . . . . . . . . . . . . . . . . . . . . 96
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

8 The IO monad and fixIO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
8.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
8.2 Motivating examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
8.3 The language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
8.4 Semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
8.4.1 IO layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
8.4.2 Functional layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
8.4.3 The marriage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
8.4.4 Structural rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
8.4.5 Meaning of program states . . . . . . . . . . . . . . . . . . . . . . . 112
8.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
8.6 Properties of fixIO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
8.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
9 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
9.1 The repmin problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
9.2 Sorting networks and screen layout in GUI’s . . . . . . . . . . . . . . . . . . 125
9.3 Interpreters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
9.4 Doubly linked circular lists with mutable nodes . . . . . . . . . . . . . . . . 128
9.5 Logical variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
9.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
10 Epilogue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
10.1 Related work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
10.2 Future Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
A Fixed-point operators. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

B Proofs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
B.1 Proposition 2.5.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
B.2 Proposition 2.6.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
B.3 Proposition 2.7.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
B.4 Lemma 3.1.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
B.5 Proposition 3.4.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
B.6 Proposition 4.3.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
B.7 Proposition 4.9.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
B.8 Proposition 6.3.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Bibliography . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Biographical Note . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162vii

Abstract
Value Recursion in Monadic Computations
Levent Erkök
Ph.D., OGI School of Science and Engineering,
Oregon Health and Science University
October 2002
Thesis Advisor: Dr. John Launchbury

This thesis addresses the interaction between recursive declarations and computational
effects modeled by monads. More specifically, we present a framework for modeling cyclic
definitions resulting from the values of monadic actions. We introduce the term value
recursion to capture this kind of recursion.
Our model of value recursion relies on the existence of particular fixed-point operatorsfor individual monads, whose behavior is axiomatized via a number of equational properties. These properties regulate the interaction between monadic effects and recursive
computations, giving rise to a characterization of the required recursion operation. We
present a collection of such operators for monads that are frequently used in functional
programming, including those that model exceptions, non-determinism, input-output, andstateful computations.
In the context of the programming language Haskell, practical applications of value
recursion give rise to the need for a new language construct, providing support for recursive monadic bindings. We discuss the design and implementation of an extension to
Haskell’s do-notation which allows variables to be bound recursively, eliminating the needfor programming with explicit fixed-point operators.

viiiChapter 1
Introduction
This thesis addresses the interaction between two fundamental notions in programming
languages: Recursion and effects. Recursion is the essence of cyclic definitions, both forrecursive functions and circular data structures. Effects are the essence of computationalfeatures, including I/O, exceptions, and stateful computations. Although both notions
have been studied extensively on their own, their interaction has received relatively little
attention.

1.1

Recursion and effects

In the traditional domain theoretic setting, the denotational semantics of recursive definitions are understood in terms of fixed-points of continuous functions. That is, thesemantics of a definition of the form x = f x is taken to be the least fixed-point of themap corresponding to f [82, 83]. The same principle works for both recursive functions
and circular data structures, a rather pleasing situation.
Handling of effects in the denotational framework, however, proved to be much more
problematic, often summed up by the phrase “denotational semantics is not modular” [53,64]. Briefly, addition of new effects require substantial changes to the existing semantic
description. For instance, exceptions can be modeled by adding a special failure element toeach domain, representing the result of a failed computation. But then, even such a simple
thing as the meaning of an arithmetic operation requires a messy denotational description;
one needs to check for failure at each argument, and propagate accordingly. The story issimilar for other cases, including I/O and assignments, two of the most “popular” effects
found in many programming languages [76, 77].
It was Moggi’s influential work on monads that revolutionized the semantic treatmentof effects, which he referred to as notions of computation. Moggi showed how monads canbe used to model programming language features in a uniform way, providing an abstract
view of programming languages [62, 63]. In the monadic framework, values of a given type1

2

are distinguished from computations that yield values of that type. Since the monadic
structure hides the details of how computations are internally represented and composed,
programmers and language designers work in a much more flexible environment. This
flexibility is a huge win over the traditional approach, where everything has to be explicit.
Perhaps what Moggi did not quite envision was the response from the functional programming community, who took the idea to heart. Wadler wrote a series of articles
showing how monads can be used in structuring functional programs themselves, not justthe underlying semantics [89, 91]. Very quickly, the Haskell committee adopted monadic
I/O as the standard means of performing input and output in Haskell, making monads an
integral part of a modern programming language [68, 69]. The use of monads in Haskell
is further encouraged by special syntactic support, known as the do-notation [47].
As the monadic programming style became more and more popular in Haskell, programmers started realizing certain shortcomings. For instance, function application becomes tedious in the presence of effects. Or, the if-then-else construct becomes unsightly
when the test expression is monadic. However, these are mainly syntactic issues thatcan easily be worked around. More seriously, the monadic sublanguage lacks support forrecursion over the values of monadic actions. The issue is not merely syntactic; it is simply not clear what a recursive definition means when the defining expression can perform
monadic effects.
This problem brings us to the subject matter of the present work: Semantics of recursive declarations in monadic computations. More specifically, our aim is to study recursion
resulting from the cyclic use of values in monadic actions. We use the term value recursion
to describe this notion.

1.2

A motivating example: Modeling circuits using monads

To illustrate value recursion, we will consider the example that motivated our work in
the first place: modeling circuits using monads. Microarchitectural design languages havebeen the target of programming language research in recent years, aiming at providing
better language support for managing the complexity of such designs [12, 58]. Lava [8]and Hawk [49, 59] are two recent systems designed to address this need. In this section,
we will consider a stripped down version of such a language, embedded in Haskell.
To familiarize ourselves with the types of circuits we can define, let us first consider
a simple non-monadic implementation. We represent signals by lists, successive elements
representing the values at each clock tick. Haskell is already expressive enough to define
the basic building blocks without much difficulty:

3type Sig α = [α]and , xor :: Sig Bool → Sig Bool → Sig Booland xs ys = zipWith (&&) xs ys
xor xs ys = zipWith (6=) xs ys
inv:: Sig Bool → Sig Boolinv xs = map not xs
delay
delay

:: String → α → Sig α → Sig αv xs = v : xs

The delay element forms a signal that behaves as its second argument during the firstclock cycle, behaving as its third argument afterwards. (The first argument to delay isintended to be a name for v. We will use it later.) Of course, a more realistic example
would come equipped with multiplexers, registers, etc., but the elements above will be
sufficient for our purposes. For instance, we can model a half-adder simply by:halfAdd:: Sig Bool → Sig Bool → (Sig Bool , Sig Bool)
halfAdd xs ys = (sum, carry)
where sum = xor xs ys
carry = and xs ys
Here is a sample run:
Main> halfAdd [True, True] [False, True]([True,False],[False,True])As another example, we can create a circuit that toggles its output at each clock tick,
starting from the value False:
inp

DELAY False

out

INV

toggle :: Sig Bool
toggle = out
where inp = inv out
out = delay “False” False inp

Variables inp and out are defined mutually recursively, corresponding to the feedback
loop in the circuit diagram. The recursive definition capability of Haskell’s where clauseplays a crucial role in expressing the required cyclic dependency. We have:
Main> toggle[False,True,False,True,False,True,False,True,...
Note that the result is an infinite signal.

4What can we do with circuit descriptions? Since we model circuits by functions, we
can pass them around and combine them to build bigger circuits. But, eventually, all we
can do with a circuit is to simulate it, that is, run it on a particular input. As pointed
out by Launchbury et al. [49], and Claessen [12], this model does not allow for multiple
interpretations. Ideally, we would like to be able to analyze our circuits, translatingthem to other hardware description languages such as VHDL. Alternatively, we may want
to render the circuit graphically, obtaining a schematic diagram, or recast the circuitdescription in the language of a theorem prover to let us reason about it. We would likeour language to be flexible enough to support all of these views.
The standard way of attacking this problem is to abstract away from any particularsignal or circuit model, hiding the control flow behind a monad, and basic circuit elements
behind a type class. Each alternative semantics will be represented as an instance of thisclass, providing new views of circuits. Then, by simply switching to a different monad,
we will be able to obtain an alternative interpretation without changing existing circuitdescriptions. Here is one way of capturing the required structure: 1class Monad m ⇒ Circuit m where
and , xor :: Sig Bool → Sig Bool → m (Sig Bool )inv:: Sig Bool → m (Sig Bool )delay:: String → α → Sig α → m (Sig α)
For instance, the description of the half-adder becomes:
halfAdd:: Circuit m ⇒ Sig Bool → Sig Bool → m (Sig Bool , Sig Bool)
halfAdd i1 i2 = do sum ← xor i1 i2
carry ← and i1 i2
return (sum, carry)
Note that the new model of halfAdd is not committed to any particular circuit model,
or signal data type. It is a generic description of half-adders. To simulate, all we need is
the identity monad for expressing the control structure, and the list model for signals:
type Sig α= [α]data Simulate α = Sim α deriving Showinstance Monad Simulate wherereturn x= Sim x
Sim x = f = f x
Unsurprisingly, the Circuit instance for the Simulate monad will simply mimic our
non-monadic implementation:
1

A better alternative would be parameterizing the Circuit class over the Sig type as well, using a
multiparameter type class. We refrain from doing so, however, for the sake of simplicity.

5

instance Circuit Simulate whereand xs ys= return (zipWith (&&) xs ys)
xor xs ys= return (zipWith (6=) xs ys)
inv xs= return (map not xs)
delay v xs = return (v :xs)
Using this model, we have:
Main> halfAdd [True, True] [False, True] :: Simulate ([Bool], [Bool])
Sim ([True,False],[False,True])
More interestingly, we can consider an alternative semantics which will create a wireby-wire description of a given circuit. In this model, signals will be identified by symbolic
names. Our monad will have to generate new names for intermediate wires, accumulatinga textual “drawing” of the circuit as it is built. Hence, we employ a combination of stateand output monads:
type Sig α = String
data Draw α = D (Int → (α, [String], Int))instance Show α ⇒ Show (Draw α) whereshow (D f ) = let (l , s, ) = f 0
in concatMap (++“\n”) s ++ “Result: ” ++ show linstance Monad Draw wherereturn x= D (λi. (x , [ ], i))
D f = g = D (λi. let (a, o, i 0 ) = f i
D h
= g a
(b, o 0 , i 00 ) = h i 0in (b, o ++ o 0 , i 00 ))We will need the following auxiliary functions:
newWire :: Draw String
newWire = D (λi. (’w’:show i, [ ], i+1))
output:: String → Draw ()output s = D (λi. ((), [s], i))
item:: String → String → String → Draw String
item a b c = do n ← newWire
output (n ++ “ = ” ++ a ++ “ ” ++ b ++ “ ” ++ c)return n
The function newWire simply returns a new name. (The variable i keeps track of thenumber of wires.) The function output lets us emit intermediate descriptions. Finally,

6

item is a generic function for creating a new wire together with a description of how it isobtained. Using these auxiliaries, the Circuit instance for the Draw monad becomes: 2
instance Circuit Draw where
and a b= item “and” a b
xor a b= item “xor” a bdelay s v a = item “delay” s a
inv a= do n ← newWire
output (n ++ “ = inv ” ++ a)return n
We have:
Main> print (halfAdd "a" "b" :: Draw (Sig Bool, Sig Bool))w0 = xor a b
w1 = and a b
Result: ("w0","w1")
It is worth emphasizing that the description of halfAdd did not change, we simply useda different monad. This is the strength of the monadic approach.
Unfortunately, a similar translation for the toggle circuit does not work. Consider:
toggle :: Circuit m ⇒ m (Sig Bool )toggle = do inp ← inv out
out ← delay “False” False inp
return out
Although the description perfectly fits the circuit diagram we had before, we have lost the
feedback loop. The variables inp and out are no longer recursively defined! (In fact, thedefinition above is not even valid Haskell; the variable out is not in scope in the first line.)
Our non-monadic implementation did not have this problem, as it relied on the recursive
definition capabilities of Haskell. But now, we are on our own: Haskell does not let us
write recursive specifications in the presence of monadic effects.
Unfortunately, the problem is not merely syntactic. It is not clear how to perform thiskind of recursion at all: we want the values (i.e., the signals) to be defined recursively, but
we certainly do not want the effects to be repeated or lost (i.e., we do not want to create
circuit elements repeatedly, or not to create them at all). We refer to this kind of recursion
as value recursion. In short, to be able to express the required recursive structure, we need
the underlying monad to support recursive monadic bindings [18]. Just as the usual fixedpoint operator handles “normal” recursion, we expect to find value recursion operators,
2

The delay element did not use its first argument in the simulation model, and here it does not use
the second. The name is irrelevant for simulation, while it is all we need in a textual representation.

7

generically called mfix, mediating the interaction between the underlying effect and therecursion operation.
Getting back to circuit modeling, we will require circuits to be modeled by monads forwhich such fixed-point operators are available, captured by the MonadFix class:class Monad m ⇒ MonadFix m where
mfix :: (α → m α) → m αclass MonadFix m ⇒ Circuit m where-- and , xor , inv , delay as before
Now, we can tie the recursive knot over inp and out, expressing toggle as follows:
toggle :: Circuit m ⇒ m (Sig Bool)
toggle = mfix (λ ˜ (inp, out). do inp ← inv out
out ← delay “False” False inp
return (inp, out))
= λ(inp, out). return out
The final missing piece is the MonadFix instances for Simulate and Draw monads. At
this point, we ask the reader to simply accept the following definitions:instance MonadFix Simulate where
mfix f = Sim (let Sim a = f a in a)
instance MonadFix Draw where
mfix f = D (λi. let D g= f a
0(a, s, i ) = g iin (a, s, i 0 ))Note that the Simulate instance is essentially the same as the usual fixed-point operator. The Draw instance is a bit more complicated, but the reader can see that we performthe fixed-point computation over the variable a, (i.e., the value), passing around i and s
untouched. Now, to simulate toggle, we just use our Simulate monad:
Main> toggle :: Simulate (Sig Bool)
Sim [False,True,False,True,False,True,False,...
and, to get a simple textual drawing, we simply switch to the Draw monad:
Main> toggle :: Draw (Sig Bool)
w0 = inv w1
w1 = delay False w0
Result: "w1"
The handling of recursion via mfix is somewhat mysterious at this point. The whole
point of this thesis is to expose the mystery, and to explore the interaction between
recursion and effects, heading toward an equational theory of value recursion.

8

1.3

Recursive monadic bindings

The use of mfix to tie the recursive knot in a monadic computation is similar to the handlingof recursive bindings in usual let-expressions. For clarity, we will use the keyword letrec
here when a binding can be recursive, and let otherwise. In the pure world, we have:letrec x = e in e 0≡ let x = fix (λx. e) in e 0≡ (λx. e 0 ) (fix (λx. e))
What happens in a monadic computation? Similar to letrec, let us use the keyword
mdo3 for monadic bindings that can be recursive, and do otherwise. We have:mdo { x ← e; e 0 }≡ do { x ← mfix (λx. e); e 0 }≡ mfix (λx. e) = λx. e 0
In Chapter 7, we will describe an extension to the do-notation of Haskell allowing
bindings to be recursive, using an enhanced version of this translation. Then, we will beable to write the toggle example of the previous section as follows, the compiler taking
care of the insertion of appropriate calls to mfix :toggle = mdo inp ← inv out
out ← delay “False” False inp
return out
There is an opportunity here to clarify a potentially confusing issue about value recursion. Consider a recursive definition of the form:
countDown n = if n == 0
then print “Done!”else do print n
countDown (n−1)
The intention is clear: Each time countDown is called, we want the effect of printing totake place. In this thesis, we will not be dealing with such definitions, as they are alreadyexplained in terms of the usual fixed-point construction:
countDown = fix (λf. λn. if n == 0
then print “Done!”else do print nf (n−1))
3

The closest we can get to µdo using ASCII. (We would have used dorec, but that is just too long.) Notethat the use of Haskell-like syntax is just for convenience. We could have used Moggi’s let T x ⇐ e in e0
notation and the keyword letrecT as well [63].

9Note that effects are part of countDown’s execution, rather than its definition. Thatis, the effect of printing is not performed to determine the meaning of countDown itself.
In the toggle example, however, we see that effects are part of the definition: They are
performed in order to determine the values of inp and out, and the cyclic dependence
gives rise to the need for value recursion. In a sense, the use of recursion and effects incountDown are orthogonal, with no interference in between. As shown above, this kind ofrecursion is already explained in terms of fix, the usual fixed-point operator.

1.4

A generic mfix ?

In Section 1.2, we saw two particular examples of mfix, one for the Simulation monad,
and another one for the Draw monad. Are these functions actually instances of a genericschema? That is, can we find a definition of mfix that will work for all monads, regardlessof which kind of effect we deal with? Let us pause briefly and consider how one might goabout defining such a generic operator.
Recall that the least fixed-point operator on domains satisfies the property:fix:: (α → α) → α

fix f = f (fix f )which also serves as a definition for fix in a lazy language such as Haskell. One might
think that a similar defining equation can be found for mfix as well. Indeed, it is not hardto generalize to the monadic case:mfix:: Monad m ⇒ (α → m α) → m α

mfix f = mfix f = fNote that this definition makes sense for all monads (i.e., it is polymorphic in m). But
is it a “good” definition? That is, can we use it sensibly to implement value recursion?
The short answer to this question is, unfortunately, no. To see why not, simply notethat this definition is equivalent to:Gmfix f = fix (λm. m = f ) ={⊥, ⊥ = f, ⊥ = f = f, . . .}which will diverge whenever the = operator is strict in its first argument. 4 Furthermore,even when = is not strict, this definition will attempt to compute the fixed-point over4
Note that the call to mfix f will diverge regardless of what f is. In general, monads based on sum
types will suffer from this problem, as the = operator needs to inspect its first argument to see how toproceed. Haskell’s maybe and list monads are two popular examples that are based on sum types. Other
important examples where the = operator is strict in its first argument include the frequently used IO
and strict state monads.

10

both values and effects, which is simply not what we are trying to achieve. In value
recursion, we want the fixed-point to be computed only over the values, without repeating
or losing the effects. We will codify what we mean by value recursion in Chapter 2 using
a number of equational properties, exploring the interaction between recursion and effects
in depth. Then, we will be able to see more clearly why this default definition is not
appropriate for implementing value recursion.

1.5

The basic framework and notation

For most of this thesis we investigate value recursion in the usual domain theoretic semantics of programming languages, where types are modeled by domains [77, 82]. We write ⊥τ
for the least element of the domain representing the type τ , dropping the subscript whenever unambiguous. Functions are modeled by continuous (and hence monotonic) maps
between domains, not necessarily strict. Recursion is modeled via least-fixed points. We
use monads to model effects, following Moggi [63]. Although by no means comprehensive,
the reader may find it useful to skim over Appendix A, which contains a brief review of
fixed-point operators.
We expect readers to be familiar with functional programming [35, 87], particularly
Haskell [7, 68]. For the most part, we use Haskell simply as a syntactically beefed up version of λ-calculus [30], so familiarity with any functional language should be sufficient. A
basic understanding of domain theoretic semantics of programming languages is necessary
to follow the technical development [76, 83]. Except for Chapter 6, we will be mainly interested in the “functional programming view” of monads [4, 91], rather than the categoricalone [2, 55]. Finally, we will have occasion to use the parametricity principle, allowing us
to derive theorems from the types of polymorphic functions [50, 75, 88].
Naturally, the theory of value recursion is independent of any particular programming
language. However, our work is closely tied to Haskell, and we will be careful in pointing
out the cases when the domain theoretic semantics and the semantics of Haskell do notquite match up. The main differences show up in the treatment of products. Since tuples
are lifted in Haskell, it is not the case that (⊥α , ⊥β ) = ⊥α×β . Therefore, the equality
x = (π1 x, π2 x) fails. Similarly, λ(x :: α).⊥β 6= ⊥α→β , i.e., the function type is lifted too.
Similar comments apply to sum types as well. Finally, the unit data type has two members,⊥() and () itself, that is, it is not really a terminal object. Luckily, these differences do
not cause much trouble in practice, as long as one is aware of them. We point out thecases where the difference becomes significant.
In our exposition, we will stick to Haskell notation as much as possible, deviatingfrom it only for typographical purposes. The difference mainly shows up in compositions

11and λ-bindings. For instance, we will write Haskell’s: \f -> \g -> \x -> (f . g) x as
λf. λg. λx. (f · g) x.

1.6

Outline of the thesis

Our aim is to get through the basics of value recursion rather quickly, before we actuallyinvestigate individual instances. To this end, we use the next two chapters to introduce
a number of equational properties that govern the behavior of value recursion operators.
Among these, we will identify three fundamental properties (namely strictness, purity, andleft shrinking), and in the remainder of the thesis we will consider only those operatorsthat satisfy this minimal core.
Chapters 4 and 5 are dedicated to the study of individual instances. In Chapter 4, weinvestigate a wide range of monads that are frequently used in functional programming,presenting value recursion operators for them. In Chapter 5, we argue that it is highly
unlikely that the continuation monad has an associated value recursion operator that will
satisfy our requirements.
Chapter 6 takes a step back and looks at a possible categorical theory of value recursion,
based on the notion of premonoidal categories and traces. Even though the theory of traces
does not provide a perfect fit, it is illuminating to see how recent work in this area can begeneralized to capture value recursion for a certain class of monads.
Chapters 7 and 8 deal with the Haskell language in particular. In Chapter 7, we willturn our attention to syntactic support for value recursion, presenting a recursive versionof Haskell’s do-notation. In Chapter 8, we will study Haskell’s IO monad. Since the IO
monad is hardwired into Haskell, it is not possible to investigate value recursion for it
directly. Hence, we present a model language (complete with I/O operations and mutablevariables), and show how one can model value recursion in this world.
Chapter 9 presents a number of examples, which, in addition to the circuit modelingexample of this chapter, provides a tour of potential applications of value recursion.
Chapter 10 concludes the thesis with a discussion of related work and future research
directions. A brief review of fixed-points, along with several proofs that are omitted fromthe main body of the thesis are given in the appendices.
Each chapter in the remainder of this thesis starts with a brief description of its
contents. Although we intend the chapters to be read in order, readers may find it useful
to quickly skim over these segments to determine a particular reading plan according totheir own interests.

Chapter 2
Properties of value recursion operators
What kinds of properties do we expect value recursion operators to satisfy? So far, we havebeen using phrases like “recursion without repeating or losing effects,” or “recursion only
over the values” to characterize value recursion. The aim of this chapter is to formalize
our intuitions by means of equational properties.
Synopsis. We discuss a number of equivalences that we expect value recursion operators
to satisfy. These properties range from those that imitate properties of the usual fixedpoint operator over domains, to those that govern the interaction between recursion andeffects. We also provide a number of derived properties, including those that are granted byvirtue of parametricity. Several properties that might be naively expected, yet unsatisfiable
for a wide range of monads, are discussed as well.

2.1

Strictness (Nothing from nothing)

The domain theoretic treatment of recursion in programming languages relies on least
fixed-points [76, 83]. That is, given a specification of the form x = f x, where f :: α → α,
we expect x to be the least α satisfying this equation. In this setting, one can show that afunction is strict if and only if its least fixed-point is ⊥. Since ⊥ represents no informationin the domain theoretic ordering, our slogan in this case is simply nothing from nothing.
Generalizing to value recursion, we expect the following property to hold:
Property 2.1.1 (Strictness.) Let f :: α → m α,f ⊥α = ⊥m α⇔

mfix f = ⊥m α

(2.1)

Remark 2.1.2 In Section 2.6.2, we will be able to derive the right to left implicationfrom other properties, i.e., we will show that if mfix f is ⊥, then f must be strict. We
prefer expressing the strictness law as it is, however, as it uniquely characterizes strict
functions of type α → m α.
12

13

2.2

Purity (Just like fix )Purity formalizes the intuition that mfix should behave exactly like fix, in case there areno effects:
Property 2.2.1 (Purity.) Let h :: α → α,
mfix (return · h) = return (fix h)(2.2)

Diagrammatically, we capture purity as follows:hreturn=hreturnRemark 2.2.2 We use wiring diagrams to capture properties pictorially. Note that we
do not formalize these diagrams, nor use them for any purpose other than illustration.
Dashed boxes represent where value recursion is performed. Thin lines show data flow.
The thick line, called the effect line, refers to the details of the monadic computation.
Although it is not correct to consider the effect line as carrying data, it usually helps tothink of it as such. (The effect line analogy holds very well for the state monad, but it is
not very intuitive for, say, the exception monad.) We indicate pure computations by notletting them use the effect line, as illustrated by the h box in the above diagram. The
solid loop on the right hand side indicates the use of fix. (Note that there are no dashedboxes on the right hand side as there are no applications of mfix.)

2.3

Left shrinking (No recursion – No fix )Recall our naive translation schema for the recursive do-notation from Section 1.3. Naturally, we would like mdo to behave exactly like do, provided there are no recursive
bindings. That is, the following two code fragments should have the same meaning:
mdo x ← A
B

do x ← A
mdo B

provided A does not make use of x, or any variable defined in the block B. If B does nothave any recursive bindings either, we can push mdo further down, eventually eliminating
it altogether. We capture this correspondence by the left shrinking property:

14

Property 2.3.1 (Left shrinking.) Let f :: α → β → m α, a :: m β,
mfix (λx. a = λy. f x y) = a = λy. mfix (λx. f x y)

(2.3)

where x does not occur free in a.
The name “left shrinking” is suggested by the corresponding diagram:
x
y
a

=

f

x
y
a

f

Remark 2.3.2 The reader might expect an analogous right shrinking property as well.
But, as we will see in Chapter 3, arbitrary lifting of computations from the right handside of a = is not possible in general. We can, however, lift pure computations out. We
will provide a derived law to deal with this case in Section 2.6.3.

2.4

Sliding (Pure mobility)

Let f :: α → β and h :: β → α. As reviewed in Appendix A, the equation
fix (h · f ) = h (fix (f · h))(2.4)

expresses the dinaturality condition for fix, an extremely important law for manipulating
fixed-points. We expect value recursion operators to satisfy a similar law as well.
Two problems arise in translating Equation 2.4 to the world of value recursion. The
order of f and h is swapped, and h is duplicated on the right hand side. Obviously, if fand h can both perform effects, swapping and duplication are both out of question. Whenh is pure, however, we expect to be able to slide it over f :Property 2.4.1 (Sliding.) Let f :: α → m β, h :: β → α,f (h ⊥) = f ⊥ ⇒ mfix (map h · f ) = map h (mfix (f · h))(2.5)

where map :: (a → b) → m a → m b is the usual lifting function. 1 The consequentcan be equivalently expressed as:
mfix (λx. f x = return · h) = mfix (f · h) = return · h
1

(2.6)

The function map is defined by the equation map f m = m = return · f. Note that, in Haskell
notation map is called fmap, and the name map is reserved to be used with the list monad only [68].
Deviating from Haskell, we use the name map consistently for all monads.

15

Diagrammatically:

f

h

=

h

h

f

The side condition, i.e., f · h and f should agree on ⊥, is essential. When we think ofrecursion as an iterative process that starts with ⊥, we see that f first receives ⊥ on theleft hand side in the recursive loop, but receives h ⊥ on the right. If h ⊥ 6= ⊥, f will havemore information to start with on the right hand side. The side condition guarantees that
this extra knowledge is irrelevant: f must not distinguish between ⊥ and h ⊥. It is worth
noting that dinaturality of fix (Equation 2.4) does not require any such conditions. As
we will see in Chapter 3, however, without the side condition, sliding is unsatisfiable formany practical monads of interest.
Observation 2.4.2 The side condition is trivially satisfied if h is strict. It turns outthat this particular case is derivable from parametricity (see Corollary 2.6.12).
Note The alert reader will note that the order of effects does not matter for commutative
monads, and hence one might expect a swapping property where both computations are
effectful. This is indeed the case, see Section 3.3 for details.

2.5

Nesting (Two for the price of one)

Bekič’s property for usual fixed-points states that simultaneous recursion over multiple
variables is equivalent to recursion over one variable at a time (see Appendix A.) In thevalue recursion world, one way to express this relation is to assert the equivalence of thefollowing two expressions:mdo x ← mdo y ← f (x , y)return yreturn x

mdo x ← f (x , x )return x

The nesting property2 stipulates this equivalence:
Property 2.5.1 (Nesting.) Let f :: (α, α) → m α,
mfix (λx. mfix (λy. f (x , y))) = mfix (λx. f (x , x ))2This property was first suggested to us by Ross Paterson (personal communication).

(2.7)

16

The following proposition states an equivalent form of nesting, which is quite useful insymbolic manipulations:
Proposition 2.5.2 Let f :: (τ, σ) → m (τ, σ). Assuming true products, the equation(2.8)

mfix (λ(x , ). mfix (λ( , y). f (x , y))) = mfix f
is satisfied exactly when nesting holds, provided mfix satisfies the sliding property.
Proof

See Appendix B.1.



Using Equation 2.8, it is easy to describe nesting diagrammatically:

f

y
x

=

f

y
x

Just like the Bekič property for fix, nesting generalizes to any number of variables. For
instance, one can derive:
mfix (λ(x ,, ). mfix (λ( , y, ). mfix (λ( , , z ). f (x , y, z ))))

= mfix (λ(x , y, z ). f (x , y, z ))(2.9)

Note that the order of nesting is also immaterial, we could have recursed over any permutation of the variables; for instance, first over z, then x and finally y, etc.
Remark 2.5.3 We will take a closer look at Equation 2.8 in the case of lifted products(as in Haskell). Assuming mfix satisfies strictness, the left hand side will always be ⊥, due
to strict matching against pairs. Using irrefutable patterns, one might attempt:
mfix (λ˜ (x , ). mfix (λ˜ ( , y), f (x , y))) = mfix f
However, a problem still remains. If f is strict, then the right hand side will be ⊥, but
the left hand side might produce an answer, because ⊥ 6= (⊥, ⊥). 3 The proper way of
expressing Equation 2.8 with lifted products is:
mfix (λ˜ (x , ). mfix (λ˜ ( , y), f (x , y))) = mfix (λ˜ (x , y). f (x , y))(2.10)

Similar to Proposition 2.5.2, one can establish:
Proposition 2.5.4 In the case of lifted products, Equation 2.7 is equivalent to Equation 2.10, provided mfix satisfies sliding.
3Peter Thiemann was first to notice this problem (personal communication).



17

2.6

Derived properties

One can derive new equalities using the properties we have described so far, and properties of the underlying domain-theoretic framework. This section presents a collection ofsuch laws—those that we have found to be the most useful when reasoning about value
recursion.

2.6.1

Constant functions

Left shrinking and purity properties imply an expected property of fixed-point operators:
If the fixed-point variable is not used, recursion is irrelevant:Proposition 2.6.1 Let a :: m α be a constant (i.e., x does not occur free in a). Then,(2.11)

mfix (λx.a) = a
provided mfix satisfies purity and left shrinking laws.
Proofmfix (λx. a) = mfix (λx. a = λy. return y)= a = λy. mfix (λx. return y)= a = λy. return (fix (λx. y)){left shrinking}{purity}= a = λy. return y= aNote that fix (λx. y) = (λx. y) (fix (λx. y)) = y.



The diagram in this case is trivial:a=aSimilarly, we can lift a conditional expression from inside an mfix, if the test expression
is not involved in the recursion computation:
Proposition 2.6.2 Let a be a boolean expression where x does not occur free in a. Letf, g :: α → m α. We have:
mfix (λx. if a then f x else g x ) = if a then mfix f else mfix g
Proof(2.12)

Case analysis on the value of a. The True and False cases are obvious. Whena = ⊥, the left hand side yields ⊥ by Proposition 2.6.1, guaranteeing the equivalence. 

18

2.6.2

Approximation property

Monotonicity implies that f ⊥ always provides an approximation to mfix f :Proposition 2.6.3 Let f :: α → m α. Then,f ⊥ v mfix fprovided mfix satisfies purity and left shrinking.
Proof

Since (λx. f ⊥) v (λx. f x), we have mfix (λx. f ⊥) v mfix f by the monotonic-

ity of mfix. But the left hand side is f ⊥ by Proposition 2.6.1, completing the proof.

Remark 2.6.4 Proposition 2.6.3 states more than a rudimentary fact: f ⊥ yields valuable information on the structure of the fixed-point. Consider the list monad, for instance.
If f :: a → [a], and if f ⊥ is a cons-cell, then so is mfix f . In particular, if f ⊥ is a finite
list of length k, then the length of the fixed-point is k as well. In general, for any monadbased on a sum type, f ⊥ determines the top level structure of mfix f.
We can now establish the strictness property in one direction (see Remark 2.1.2):
Corollary 2.6.5 Let f :: α → m α, and mfix f = ⊥. Then f is strict, provided mfix
satisfies purity and left shrinking laws.
Proof

By Proposition 2.6.3, f ⊥ v ⊥, implying that f ⊥ = ⊥.

2.6.3

Pure right shrinkingThe sliding property allows lifting of pure computations from the right hand side of a =:
Corollary 2.6.6 Let f :: α → m α, and h :: α → β,mfix (λ(x , y). f x = λz. return (z , h z ))= mfix f = λz. return (z , h z )(2.13)

provided mfix satisfies sliding. (On the left hand side, the value-recursion loop is over(α, β), while the one on the right hand side it is over α only.)
Proof

We have

mfix (λ(x , y). f x = λz. return (z , h z ))= mfix (map (λz. (z , h z )) · f · π1 )

{slide}= map (λz. (z , h z )) (mfix (f · π1 · (λz. (z , h z ))))
= mfix f = λz. return (z , h z )Sliding applies, since (f · π1 ) ⊥ = (f · π1 · λz.(z, h z)) ⊥ = f ⊥.

19

The diagram in this case looks like:
x
f

y

h

=

x

f

h

which suggests the name pure right shrinking.
Warning 2.6.7 In case we have lifted products, as in Haskell, the pattern matches
against pairs should be done lazily. That is, every formula of the form: λ(x, y). f x yshould be replaced with λt. f (π1 t) (π2 t), or the Haskell equivalent λ˜ (x , y). f x y.
(And similarly for triples, quadruples, etc.) For instance, Equation 2.13 should be expressed as:
mfix (λt. f (π1 t) = λz. return (z , h z )) = mfix f = λz. return (z , h z )or,mfix (λ˜ (x , y). f x = λz. return (z , h z )) = mfix f = λz. return (z , h z )avoiding the strict match against the tuple.
It is possible to generalize Equation 2.13, so that h can use x and y as well. We callthis variant the scope change law:
Proposition 2.6.8 Let f :: α → m α, h :: α → (α, β) → β,mfix (λ(x , y). f x = λz. return (z , h z (x , y)))= mfix f = λz. return (fix (λ(x , y). (z , h z (x , y))))(2.14)

provided mfix satisfies purity, left shrinking, nesting, and sliding laws.
Proof

See Appendix B.2.



Remark 2.6.9 Simple manipulation of the right hand side of Equation 2.14 yields thefollowing equation:
mfix (λ(x , y). f x = λz. return (z , h z (x , y)))= mfix f = λz. return (z , fix (λy. h z (z , y)))(2.15)

This form of the scope changing property is quite useful in derivations, although somewhat
less symmetric than Equation 2.14.

20

2.6.4

Parametricity: The “free” theorem

The least fixed-point operator on domains satisfies the following uniformity law [60, 82]:
Let f :: α → α, g :: β → β, and s :: α → β, where s is strict. Then,s · f = g · s =⇒ s (fix f ) = fix g(2.16)

This extremely useful law is exactly the free theorem for the type (α → α) → α, andhence granted by virtue of parametricity in our setting [75]. For mfix, parametricity gives
us the following theorem for free:
Theorem 2.6.10 Let f :: α → m α, g :: β → m β, s :: α → β,map s · f = g · s =⇒ map s (mfix f ) = mfix gprovided s is strict.

(2.17)


Remark 2.6.11 It is worth emphasizing that we use Theorem 2.6.10 freely in our treatment of value recursion.4 If one takes a more abstract view, of course, we expect Equation 2.17 to be postulated as a property to be checked, rather than taken for granted. Of
course, this begs the question exactly what strict would mean in this new setting. See
Simpson and Plotkin’s recent work for a modern account of such questions [79]. (We will
return to the treatment of value recursion in more abstract settings in Chapter 6.)
As we pointed out before, sliding strict computations is a direct consequence of parametricity:
Corollary 2.6.12 Let f :: α → m β, h :: β → α. Then,
mfix (map h · f ) = map h (mfix (f · h))(2.18)

provided h is strict.
Proof

Direct consequence of the free theorem with F 7→ f · h, G 7→ map h · f andS 7→ h, where we use capital letters to identify variables in Equation 2.17.
4



A word of caution is in order regarding Haskell and parametricity. It is well known that the seq
primitive weakens the parametricity properties of Haskell [50, 68, 88]. We do not make use of this primitivein our work.

21

Parametricity allows us to take mirror images of our properties. For instance, thefollowing equation is essentially the same as Equation 2.13:
mfix (λ(x , y). f y = λz. return (h z , z )) = mfix f = λz. return (h z , z )Obviously, we can consider the same equation over arbitrary length tuples and arbitrary
permutations as well. We capture the essence of this process in the following corollary:
Corollary 2.6.13 Let f, g :: (α, β) → m (α, β). The equation mfix f = mfix g holds
exactly when its mirror image, that is:
mfix (map swap · f · swap) = mfix (map swap · g · swap)
holds, where swap (x , y) = (y, x ).
Proof

Simple application of Corollary 2.6.12 on both sides. Note that swap is strict. As a final corollary to the free theorem, we consider the following injection law:
Corollary 2.6.14 Let f :: α → m α, i :: α → β, p :: β → α, where p is strict andp · i = idα . We have:
mfix f = map p (mfix (map i · f · p))
Proof(2.19)Let F 7→ map i · f · p, G 7→ f, and S 7→ p in the free theorem. Again, capitalletters denote the variables in Equation 2.17.

Note that Corollary 2.6.14 also follows from the sliding property. The intended readingof Equation 2.19 is as follows. The function i injects α’s to β’s, while p projects back.
Hence, we can introduce spurious variables into the recursive loop, as long as they are notused anywhere.

2.7

Stronger properties

In this section we present two laws, strong sliding and right shrinking, which might benaively expected to be satisfied by value recursion operators. As we will prove in Chapter 3, however, they are both unsatisfiable for a wide variety of monads of practical interest. The most important monad satisfying both these properties is the lazy state monad(Section 4.4).

22

2.7.1

Strong sliding

If Equation 2.5 holds unconditionally, (i.e., without requiring f (h ⊥) = f ⊥), we say thatthe given value recursion operator satisfies the strong sliding property. As we will see in
Chapter 3, strong sliding is not satisfiable for a variety of practical monads. However,when available, it allows us to deduce several interesting equalities:
Proposition 2.7.1 Let f :: α → m α, and q :: α. Then,(2.20)

mfix (λ(x , ). f x = λy. return (q, y)) = f q = λy. return (q, y)
provided mfix satisfies the purity, left shrinking and strong sliding properties.
Proof

See Appendix B.3.



Proposition 2.7.2 Let f :: α → m β, g :: β → m α. Then,
mfix (λ(x , y). f x = λy 0 . g y = λx 0 . return (x 0 , y 0 ))(2.21)= mfix (λ(x , ). f x = λy 0 . g y 0 = λx 0 . return (x 0 , y 0 ))provided mfix satisfies the purity, left shrinking, nesting and strong sliding properties.
Diagrammatically:
x’
y

x
f

y’

y’
g

x’

=

x

y’

y’
f

g

Proof Straightforward applications of nesting, left shrinking, and the mirror image ofthe previous proposition on the left hand side.


2.7.2

Right shrinking

Pure right shrinking (Corollary 2.6.6) tells us how to pull pure computations from the righthand side of a =. Although it is not possible to pull out effectful computations in general,
there are certain monads for which it is possible to do so, the most important examples
being the output monad (or, in general, monads based on monoids—see Section 4.5), andthe lazy state monad (Section 4.4). The following property captures the situation:
Property 2.7.3 (Right shrinking.) Let f :: α → m α, g :: α → m β,
mfix (λ(x , y). f x = λz. g z = λw. return (z , w ))= mfix f = λz. g z = λw. return (z , w )(2.22)

23

Diagrammatically:
z
x
y

w

z
f

g

=

w

x
f

z

g

Fact 2.7.4 Obviously, Equation 2.22 generalizes 2.13. That is, if a given value recursion
operator satisfies right shrinking, it will automatically satisfy the pure version as well.
The combination of right shrinking and strong sliding allow us to generalize the scopechange law (Proposition 2.6.8) as well:
Proposition 2.7.5 Let f :: α → m α, g :: α → (α, β) → m β,
mfix (λ(x , y). f x = λz. g z (x , y) = λw. return (z , w ))= mfix f = λz. mfix (λb. g z (z , b)) = λw. return (z , w )(2.23)

provided mfix satisfies purity, left shrinking, nesting, strong sliding and right shrinking.
Proof

2.8

Analogous to the proof of Proposition 2.6.8.

Classification and summary

Our properties try to capture the expected behavior of value recursion operators, formalizing our intuitions. It is worth reiterating the most important goals:• Recursion should be performed only over the values, and the fixed-point computation
should be similar to that of fix,
• Effects should be neither repeated nor lost,• In the case when there are no recursively bound variables, mdo should behave
exactly like a do.
How do our properties match these goals? Strictness states that the fixed-point is ⊥exactly when the given function is strict, analogous to fix. Purity states that, in case there
are no effects, mfix should behave exactly like fix. These two properties are as close as we
get to the behavior of the usual fixed-point operator on domains. Left shrinking statesthat mdo is exactly the same as do, in case there are no recursive bindings. We consider
these three properties to be the most essential, leading to the following definition:

24

Definition 2.8.1 (Value recursion operators.) A value recursion operator for a monad(m, =, return) is a function mfix :: (α → m α) → m α, satisfying:• Strictness: f ⊥α = ⊥m α⇔

mfix f = ⊥m α ,• Purity: mfix (return · h) = return (fix h),• Left shrinking: mfix (λx. a = λy. f x y) = a = λy. mfix (λx. f x y), provided x is not free in a.
At this point, two questions arise. First, why are sliding and nesting properties left
out from Definition 2.8.1, even though we have found that they are both satisfied by many
instances of mfix in practice (see Chapter 4)? And second, are there other properties ofinterest that we have completely missed?
The answer to the first question is a matter of choice. We would like to keep therequirements as simple as possible, but no simpler. As we will see several examples in
Chapter 4, operators that do not satisfy the basic properties mandated by Definition 2.8.1
yield results that are not very sensible for value recursion. Other properties are just as
important theoretically, but it is our belief that they are in a secondary status from apractical point of view.
It is much harder to answer the second question. Whether we have the “right” definition should become apparent as value recursion finds its place in practical functional
programming. Our work, both in the context of this thesis and in using recursive monadic
bindings in practical Haskell programs, led us to conclude that Definition 2.8.1 satisfactorily captures the minimal common core.
Finally, a comment on uniqueness is in order. Given a particular monad, we do not
require a unique value recursion operator for it. There may be none, exactly one, or many
operators satisfying the requirements of Definition 2.8.1. (For instance, in Chapter 4, we
will be able to show that identity, maybe and list monads of Haskell have unique value
recursion operators, while the state monad has an infinite chain of them. On the otherextreme, the continuation monad probably has none—see Chapter 5 for details.) Furthermore, different operators for the same monad might satisfy different sets of properties in
addition to the basic set mandated by Definition 2.8.1. In such a case, the user has the responsibility to pick the most appropriate operator for the problem at hand, possibly using
our properties as a guide. We will see a concrete example of this situation in Section 4.4.

Chapter 3
Structure of monads and value recursion
So far, our study of value recursion was set in the context of arbitrary monads. We will
now take a closer look at various properties that monads may satisfy, such as idempotency,
commutativity, or additivity. The aim of this chapter is to investigate the implications ofstructural properties of monads for value recursion.
Synopsis. We first consider monads whose = operator is strict in its first argument, covering many practical monads of interest. We show that strong sliding and rightshrinking properties are not satisfiable for such monads. We then consider idempotent,
commutative and additive monads, trying to identify how value recursion operators should
behave in each case. Finally, we briefly discuss embeddings and monad transformers.

3.1

Monads with a strict bind operator

Consider a monad m whose = operator is strict in its first argument. That is:⊥m τ = f = ⊥m σ

(3.1)

for all f :: τ → m σ. Haskell’s maybe, list, IO, and strict state monads are examples ofsuch monads. In this section, we will prove that neither strong sliding, nor right shrinking
properties can be satisfied for such a monad, unless it is trivial in the following sense:
Definition 3.1.1 (Trivial monad.) A monad (m, =, return) is trivial if, for all types
τ , the domain corresponding to the type m τ consists only of ⊥ m τ .
Remark 3.1.2 The canonical example of a trivial monad is:data Void α-- no constructors, all we have is ⊥return x = ⊥m = f = ⊥25

26

Note that all of our properties hold for a trivial monad, with the only possible definition
mfix f = ⊥.
Lemma 3.1.3 Let (m, =, return) be a monad where = is strict in its first argument.
If return is strict as well, then m is trivial. 1
Pick an arbitrary type τ , and let a be an arbitrary element of m τ . We have:

Proof

a

=

const a ⊥τ{const x y = x}==

returnτ ⊥τ = const a⊥m τ = const a{left unit}{return is strict}

=

⊥m τ

{= is strict}

The result now follows by Definition 3.1.1.

Note that Lemma 3.1.3 requires return to be strict at all types. The following lemma
simplifies this requirement, reducing the proof obligation to return being strict at only one
particular type:2
Lemma 3.1.4 Let (m, =, return) be a monad where = is strict in its first argument.
If return is strict at one type, (i.e., there exists a type τ s.t. return τ ⊥τ = ⊥m τ ), then it
is strict at all types.
See Appendix B.4.

Proof



After these preliminary results, we can now proceed with our original goal:
Proposition 3.1.5 Let (m, =, return) be a monad where = is strict in its firstargument. If there is a value recursion operator for m that satisfies the strong sliding
property of Section 2.7.1, then m is trivial.
We will first establish that if such an operator exists, then return must be strict.

Proof
Define:3f:: () → m ()f () = return ()hh:: () → ()= ()Note that f · h = λx. return (). Let mfix be a value recursion operator for m satisfying
the strong sliding property. Then, Equation 2.6 must hold with no side conditions. The
right hand side of Equation 2.6 reads:
1

For brevity, we simply refer to a monad (m, =, return) by the name of its type constructor, i.e., m.
This lemma and its proof has been suggested to us by Ross Paterson (personal communication).
3
The domain corresponding to the unit type, written () following the Haskell notation, consists ofexactly two elements: ⊥ and (), with the obvious ordering ⊥ ().
2

27

mfix (f · h) = return · hand, by Proposition 2.6.1 and the left unit law, it must be equal to return (). Similarly,
the left hand side of Equation 2.6 reads:
mfix (λx. f x = return · h)and, by the strictness property, it must compute to ⊥. (Note that f is strict because
it matches its argument against (), and = is strict in its first argument by hypothesis.)Hence, strong sliding implies return () = ⊥. By monotonicity, then, return must be strictat the type (). Hence, by Lemmas 3.1.4 and 3.1.3, m must be trivial.

A similar argument shows that right shrinking property shares the same fate:
Proposition 3.1.6 Let (m, =, return) be a monad where = is strict in its firstargument. If there is a value recursion operator for m that satisfies the right shrinking
property of Section 2.7.2, then m is trivial.
Define:Prooff:: [Int] → m [Int]f xs = return (1 : xs)g:: [Int] → m Int
g [x ] = return xg= return 1

It is easy to see that the left hand side of Equation 2.22 must yield ⊥ by the strictness
property (note that g will diverge on 1 : ⊥). By purity, we have
mfix f = return (fix (λxs. 1 : xs))
Hence, the right hand side of Equation 2.22 evaluates toreturn (1, fix (λxs. 1 : xs))
implying that ⊥ = return (1, fix (λxs. 1 : xs)). By monotonicity, then, return must be
strict at the type (Int, [Int]) . Hence, by Lemmas 3.1.4 and 3.1.3, m must be trivial. In other words, unless a given monad m is trivial, no value recursion operator form can satisfy strong sliding and right shrinking properties, provided m’s = operator isstrict in its first argument. This is an important result, as it identifies inherent limitations
on properties that can be expected to hold for many practical monads of interest.
Corollary 3.1.7 Neither strong sliding nor right shrinking properties are satisfiable for
Haskell’s maybe, list, strict state and IO monads, as none of these monads are trivial (nopun intended—see Definition 3.1.1).



28

3.2

Idempotent monads

A monad m is said to be idempotent if the equation 4a = λx. a = λy. return (x , y) = a = λx. return (x , x )(3.2)

holds for all a :: m τ [46]. Identity, maybe and environment monads are examples ofidempotent monads. Intuitively, a monad is idempotent if computations can be duplicatedwhenever their results are needed.
Note that Equation 3.2 does not specify any data flow between repeated computations.
That is, the equation
λx. f x = f

=

(3.3)

f

is not required to hold. 5 However, if a monad is idempotent, we expect both sides ofEquation 3.3 to be indistinguishable by mfix. Furthermore, once mfix f is computed fora function f, further applications of f should not change the result. We capture theseintuitions in the following property:
Property 3.2.1 (Idempotency.) Let f :: α → m α, where m is an idempotent monadwith a value recursion operator mfix. Then,
mfix (λx. f x = f )= mfix f(3.4)

mfix f = f

= mfix f(3.5)

The first equality can be captured diagrammatically as follows:

f

f

=

f

We leave it to the reader to picture Equation 3.5.
Remark 3.2.2 It is important to note that Property 3.2.1 does not state that Equation 3.4 or 3.5 can be used as definitions of value recursion operators whenever the underlying monad is idempotent.6 For instance, Equation 3.5 will always produce ⊥ for4

In category theory, a monad m is called idempotent if its join :: m (m α) → m α operator is an
isomorphism [55]. The definition we use is more useful from a practical point of view, however. For instance,
the maybe monad is idempotent with our definition, although its join operator is not an isomorphism.
5
As a counterexample, consider the identity monad where Equation 3.3 is satisfied only for idempotent
functions (i.e., f 2 = f ), but not in general.
6
Individual definitions might coincide, of course. For instance, in Chapter 4, we will see that Equation 3.5 does indeed define value recursion operators for identity and environments, but not for exceptions.

29

mfix in a monad with a = operator that is strict in its first argument, which is clearly
undesirable.
We will discuss idempotency property with respect to identity, exception, monads
based on idempotent monoids, and environments in Chapter 4.

3.3

Commutative monads

A monad m is said to be commutative if the order of effects does not matter. That is, if
the equation
λ(x , y). f x = λx 0 . g y = λy 0 . return (x 0 , y 0 )(3.6)= λ(x , y). g y = λy 0 . f x = λx 0 . return (x 0 , y 0 )holds for all f :: α → m β and g :: τ → m σ. For a commutative monad, we expect mfix
to satisfy swapping of computations similarly, as depicted in the following diagram:
x’
y

y’

y’

x
f

g

x’

=

x
y

g

x’
f

y’

Property 3.3.1 (Commutativity.) Let f :: α → m β, g :: β → m α, where m is acommutative monad with a value recursion operator mfix. Then,
mfix (λ(x , ). f x = λy 0 . g y 0 = λx 0 . return (x 0 , y 0 ))= mfix (λ( , y). g y = λx 0 . f x 0 = λy 0 . return (x 0 , y 0 ))(3.7)

In case a value recursion operator satisfies nesting and strong sliding laws, Equation 3.7
can be derived automatically:
Proposition 3.3.2 Equation 3.7 follows from nesting and strong sliding laws.
Proof

Straightforward applications of Equation 2.21, Equation 3.6, nesting, left shrink-ing, and Equation 2.20 on the left hand side.



Examples of commutative monads include identity, environments, and monads basedon commutative monoids. We will investigate the commutativity property with respect
to these monads in Chapter 4.

30

3.4

Monads with addition

A monad m is said to be additive if there exists an element zero :: m τ , and an operation⊕ :: m τ → m τ → m τ , such that:zero ⊕ p = p
p ⊕ zero = p(p ⊕ q) ⊕ r = p ⊕ (q ⊕ r)

zero = f

= zerop  zero= zeroThe relation between ⊕ and = is not specified, although one generally checks for thefollowing distributive laws:(p ⊕ q) = f

= p = f ⊕ q = f(3.8)

p = (λx. q x ⊕ r x) = p = q ⊕ p = r

(3.9)

In Haskell, additive monads are captured as instances of the MonadPlus class, wherezero is called mzero and ⊕ is called mplus [68]. The maybe and list monads are instances ofthis class.7 It is interesting to note that the list monad satisfies Equation 3.8, but not 3.9;
while the maybe monad satisfies Equation 3.9, but not 3.8.
For an additive monad, we expect the following property to hold:
Property 3.4.1 (Distributivity.) Let m be an additive monad with ⊕ as the binary
operator. Let mfix be a value recursion operator for m. Distributivity states:
mfix (λx. f x ⊕ g x) = mfix f ⊕ mfix g(3.10)

If Equation 3.8 holds, left shrinking is sufficient to establish the distributivity property:
Proposition 3.4.2 Let m be an additive monad with ⊕ as the binary operator, and let
mfix be a value recursion operator for m. If ⊕ satisfies Equation 3.8, then mfix will satisfy
distributivity.
Proof

See Appendix B.5.



Remark 3.4.3 It is worth noting that Equation 3.8 is a sufficient, but not a necessary
condition for satisfying distributivity. As we will see in Chapter 4, the maybe monad does
not satisfy Equation 3.8, yet it has a value recursion operator satisfying distributivity.
7

In fact, the law p  zero = zero fails for both the maybe and list monads when p = ⊥. This
discrepancy does not cause any trouble for our purposes. (Recall: m  k = m = λ .k.)

31

3.5

Embeddings

Consider Haskell’s maybe and list monads. Intuitively, every value of type Maybe τ canbe considered as a value of type [τ ], mapping Nothing to [ ] and Just x to [x]. In a certain
sense, the list monad is rich enough to capture the features of the maybe monad. Formally,
this relation is captured by monad homomorphisms and embeddings [53, 89]:
Definition 3.5.1 (Monad homomorphisms and embeddings.) Let m and n be two monads. A monad homomorphism,  :: m → n, is a family of functions, one for each type τ ,τ :: m τ → n τ , such that: · return m= return n

σ (k =m f ) = τ k =n σ · f(3.11)(3.12)

where k :: m τ and f :: τ → m σ. An embedding is a monad homomorphism where eachτ is monic (i.e., injective).
Equations 3.11 and 3.12 precisely describe how  interacts with the proper morphismsof the involved monads. For value recursion, we also need to specify how  and mfix
interacts:
Definition 3.5.2 (Monad homomorphisms and embeddings for value recursion.) Let mand n be two monads with respective value recursion operators mfix m and mfixn . Let
 :: m → n be a monad homomorphism or embedding. We say that  respects value
recursion if, for all f :: τ → m τ , (mfix m f ) = mfix n ( · f )(3.13)In Chapter 4, we will see several concrete examples, including the embeddings of maybe
into list, environment and output into state, and identity into any other monad.
Proposition 3.5.3 Let  : m → n be an embedding of a monad m into a monad n. Let
mfixn be a value recursion operator for n. Let g :: (τ → m τ ) → m τ be a function,satisfying the strictness property. If  satisfies Equation 3.13 where g plays the role ofmfixm , then g is a value recursion operator for m, i.e., it will satisfy purity and leftshrinking properties as well.
Proof
idea:

Simple equational reasoning. We present the left shrinking case to illustrate the32 (g (λx. a = λy. f x y))= mfix (λx.  (a = λy. f x y)){Eqn. 3.13}= mfix (λx.  a = λy.  (f x y))=  a = λy. mfix (λx.  (f x y)){Eqn. 3.12}{left shrink}=  a = λy.  (g (λx. f x y)){Eqn. 3.13}=  (a = λy. g (λx. f x y)){Eqn. 3.12}

Since  is injective, we obtain:g (λx. a = λy. f x y) = a = λy. g (λx. f x y)
showing that g satisfies left shrinking.



Remark 3.5.4 It is unfortunate that strictness is not necessarily reflected. Using theproof technique above, one gets:  (g f ) = mfix n ( · f ), but we cannot conclude that gsatisfies strictness unless  is strict. It turns out that requiring  to be strict is an overkill;
many embedding examples we will see in Chapter 4 are not strict.
Proposition 3.5.5 The sliding, nesting, strong sliding and right shrinking properties arereflected through embeddings as well. That is, if  : m → n is an embedding respecting
value recursion, and if mfixn satisfies any of these properties, then so will mfix m .
ProofSimilar to the previous proposition.

Observation 3.5.6 Composition of two embeddings is still an embedding, hence properties are reflected through multiple embeddings as well.
Is it possible to derive value recursion operators using embeddings? Intuitively, if amonad m embeds into another monad n, and if n has a value recursion operator, one
might expect to be able to derive a value recursion operator for m. In this case, we willneed the embedding to be a split monic, i.e., to possess a left inverse, in order to be able
to map results back to m. For instance, the embedding of the maybe monad into the list
monad, and its left inverse, are given by: Nothing = [ ]` [ ]

 (Just x ) = [x ]` (x :xs) = Just

= NothingxMore formally, let  :: m → n be an embedding with the left inverse  ` :: n → m, i.e.,`τ · τ = idm τ . Note that, in general, ` is not a monad homomorphism.8 Let mfixn be a
Furthermore,  and e` are not required to form a retraction pair, i.e.,  · ` 6v id [77]. In fact,  · ` isgenerally incomparable to id, as demonstrated by the embedding of maybe into list.
8

33

value recursion operator for n. When is the function:(α → m α) → m α

g

::

gf

= ` (mfixn ( · f))(3.14)

a value recursion operator for m? Since e ` is not a monad homomorphism, not all required
properties will follow automatically. Still, this construction gives a way of obtaining acandidate value recursion operator, and we can test whether  respects value recursion
with respect to it. In this case, we need to verify:( · ` ) (mfixn ( · f)) = mfixn ( · f)

(3.15)

for all f :: α → m α. If Equation 3.15 holds, Propositions 3.5.3 and 3.5.5 will be sufficientto establish properties for g automatically.
Remark 3.5.7 It is easy to see that e` will always satisfy Equation 3.11. In general,
Equation 3.12 will only be satisfied on the subset of values that are in the image of . The
maybe into list embedding given above illustrates this point. However, we suspect that thesubset of values on which Equation 3.12 is satisfied might be sufficient to establish further
properties of the derived operator. We leave the exploration of this idea for future work.

3.6

Monad transformers

Closely related to monad homomorphisms is the idea of monad transformers. It is often
the case that one wants to add new features to an already existing monad. For instance,
one can add exceptions, state or non-determinism to a monad, obtaining a monad withnew computational features. Monad transformers have been designed to solve this problem
in a modular manner. Intuitively, given a monad m, a monad transformer t yields a newmonad t m, transforming return m to returnt m and =m to =t m . Furthermore, one
requires a monad homomorphism lift τ :: m τ → t m τ , lifting computations in m to thenew monad. We refrain from going into details here, the reader is referred to the richliterature on monad transformers for details [22, 42, 53, 54].
For value recursion, we ask a similar question. Given a monad transformer t, is therea natural way of obtaining mfixt m from mfixm ? A generic approach would be to convert agiven function f :: α → t m α to a function of type α → m α, apply mfix m to get the fixedpoint m α, and transfer it back to t m α using lift. Unfortunately, to do the conversion
from α → t m α to α → m α, one would need a morphism with type t m α → m α, theinverse of lift, which is clearly not available in general.

34

On the other hand, it is generally possible to lift arbitrary value recursion operators,
provided we know the exact structure of the monad transformer. We will consider three
examples of monad transformers in Section 4.9, namely errors, environments, and state,and show how we can lift the value recursion operators through these transformers. (Thistechnique does not always work, however, as illustrated by the continuation monad transformer. See Section 5.2 for details.)

3.7

Summary

In this chapter, we have concentrated on properties of value recursion operators that follow
from the structural properties of underlying monads. As we have seen, if the = operator
is strict in its first argument, then the strong sliding and right shrinking properties cannot
be satisfied. This is an important point: there are inherent limitations on what we can
expect from recursion in the presence of effects. (We will return to this issue in Chapter 6.)
The latter part of this chapter dealt with how value recursion operators reflect properties such as idempotency, commutativity, and additivity, and how individual propertiesare reflected through monad embeddings. In Chapter 4, we will get a chance to review
these properties with respect to concrete examples of value recursion operators.

Chapter 4
A catalog of value recursion operators
In this chapter, we present value recursion operators for monads that are frequently usedin functional programming, providing a catalog of mfix’s for the working programmer.
Although there is no magic recipe, we believe that these examples present enough patterns
to guide the construction of value recursion operators for new monads.
Synopsis. We establish a framework with the identity monad and then cover exceptions, lists, state, output, environments, trees, and fudgets. The continuation monadproves to be problematic; we consider it separately in Chapter 5. We also discuss monadtransformers, enabling us to create new mfix’s from old.

4.1

Identity

The identity monad is the monad of pure values, modeling computations with no effects:
type Identity α = αreturn= id
x = f = f x
with fix as the corresponding value recursion operator, i.e:mfix:: (α → Identity α) → Identity α

mfix f = fix f(4.1)

Proposition 4.1.1 Equation 4.1 defines the unique value recursion operator for theidentity monad.
Proof

It is easy to show that fix satisfies strictness, purity, and left shrinking properties.

For uniqueness, we will show that any value recursion operator for the identity monad must
equal fix. Let mfix0 be such an operator. We have:
mfix 0 f = mfix 0 (return · f ) = return (fix f ) = fix fby using purity and the fact that return = id.
35



36

Remark 4.1.2 Although we will stick to Haskell notation, we will generally avoid using
explicit tags to reduce clutter as long as we can. For instance, for overloading purposes,
the proper way to define the identity monad and mfix in Haskell is: 1
newtype Identity α = Id { unId :: α }instance Monad Identity wherereturn x= Id x
Id x = f = f x
mfix f = fix (f · unId)
Properties It is easy to see that all of our properties hold for Equation 4.1, including
nesting, strong sliding and right shrinking. Furthermore, the identity monad is both
idempotent and commutative, and it is an easy exercise to show that Properties 3.2.1
and 3.3.1 both hold.
The identity monad embeds into any other monad n, as long as return n is monic.
The homomorphism  = returnn easily satisfies Equations 3.11-3.13, assuming n has avalue recursion operator. (In other words, the identity monad is initial in the category ofmonads and monad homomorphisms.)

4.2

Exceptions: The maybe monad

The maybe monad of Haskell can be used to model exceptions:data Maybe α = Nothing | Just α
return= JustNothing = f = Nothing
Just x = f = f x
with the following unique value recursion operator:mfix:: (α → Maybe α) → Maybe α

mfix f = fix (f · unJust)
where unJust (Just x ) = x(4.2)

Proposition 4.2.1 Equation 4.2 defines the unique value recursion operator for themaybe monad.
1

The newtype declaration avoids adding a separate ⊥ element. If a data declaration is used, = shouldmatch lazily (i.e., ˜ (Id x ) = f = f x ) to avoid strictness problems. (See Section 3.1 for details.)

37

Proof

Strictness and purity are straightforward. For left shrinking, we need to show:
mfix (λx. a = λy. f x y) = a = λy. mfix (λx. f x y)where a is a free variable. Case analysis on a suffices to show the equivalence. Whena = ⊥, both sides yield ⊥. When a = Nothing, we get Nothing. Finally, when a = Just z
for some z, both sides yield mfix (λx. f x z ).
To show uniqueness, we do a similar case analysis. If f ⊥ = ⊥, mfix f must be⊥ by strictness. If f ⊥ = Nothing, monotonicity implies that f = const Nothing, andProposition 2.6.1 guarantees that mfix f = Nothing. Finally, if f ⊥ = Just z for somez, then f must factor through Just by monotonicity, i.e., there must be a function h suchthat f = Just · h, or equivalently, h = unJust · f. Therefore,
mfix f = mfix (Just · h)
= mfix (return · h)
= return (fix h)= return (fix (unJust · f )){purity}

To summarize, we have:
mfix f = case f ⊥ ofNothing → Nothing
Just

(4.3)

→ return (fix (unJust · f ))Note that we did not make any choices in constructing Equation 4.3; the behavior of
mfix is completely dictated by the properties that must be satisfied by all value recursion
operators. We leave it to the reader to show that Equations 4.2 and 4.3 are equivalent,establishing uniqueness.
Remark 4.2.2 By Proposition 2.6.3, f ⊥ is always an approximation to mfix f, justifying the case expression in Equation 4.3. Note that the case when f ⊥ = ⊥ is implicitly
handled by pattern match failure.
Properties It is easy to show that Equation 4.2 also satisfies sliding and nesting properties. As stated in Corollary 3.1.7, strong sliding and right shrinking properties fail.
How about idempotency (Proposition 3.2.1) and commutativity (Proposition 3.3.1)?
It turns out that the exception monad is indeed idempotent (i.e., satisfies Equation 3.2).
Equations 3.4 and 3.5 are both satisfied. On the other hand, exceptions are not commutative, due to the possibility of non-termination: Nothing = λx. ⊥ = Nothing, whereas⊥ = λx. Nothing = ⊥.

Consequently the commutativity property is not applicable.

38

Finally, we consider the distributivity (Property 3.4.1). As mentioned in Section 3.4,
the maybe monad is additive:zero= Nothing
Nothing ⊕ y = y
Just x ⊕ y = Just x
To establish
mfix (λx. f x ⊕ g x) = mfix f ⊕ mfix g
it suffices to do a case analysis on f ⊥. In case f ⊥ = ⊥, both sides will yield ⊥. In casef ⊥ = N othing, we will get mfix g on both sides. Finally, if f ⊥ takes the form of a Just,
both sides will reduce to mfix f. We leave the details to the reader.
Remark 4.2.3 It is instructive to study failing definitions of mfix as well. Consider:mfix 0 f = let Just x = f x
in return xwhich is somewhat intuitive, considering how the recursive knot is tied over x. Obviously,
strictness fails. More seriously, left shrinking fails as well:
mfix 0 (λx. Nothing = λy. return 1) = Just ⊥Nothing = λy. mfix 0 (λx. return 1) = Nothing
compromising the equivalence of do and mdo expressions in the absence of recursion. We
also have mfix 0 (λx. Nothing) = Just ⊥, which is truly bizarre.

4.3

Lists

The list monad of Haskell can be used to model computations with multiple results:
return x= [x ][]
= f(x :xs) = f

= []= f x ++ (xs = f )Given a function f :: α → [α], how do we compute mfix f :: [α] ? Intuitively, we needto select a pivot value to tie the recursive knot. Consider the following two candidates:let (a : ) = f a
in f alet ( : a : ) = f a
in f a39

where we pivot over the first and the second element of the result, respectively. Of course,
there is an infinite family of such functions, one for each particular position. As we will
see later in this section, none of these alternatives give rise to a value recursion operator.
Instead, we consider a moving pivot: Rather than fixing a single pivot element for theentire computation, we compute each element in the result using its own position as thepivot element. That is, the ith element of the fixed-point of f can be selected as the
fixed-point of the function head i · f, suggesting:mfix f = fix (head · f ) : mfix (tail · f )There is a slight problem with this approach, however: It always generates an infinite list,
repeating ⊥ after reaching the actual end of the list. Luckily, there is an easy solution.
Rather than computing fix (head · f ), we can compute fix (f · head), and stop when
the result is [ ]. Putting these ideas altogether, we obtain the following operator:mfix:: (α → [α]) → [α]mfix f = case fix (f · head ) of
[]

→ []

(4.4)

(x : ) → x : mfix (tail · f )As the following proposition shows, this definition of mfix is extremely well-behaved:
Proposition 4.3.1 The function mfix given by Equation 4.4 satisfies:

Proofmfix f = ⊥⇔ f ⊥ = ⊥(4.5)

mfix f = [ ]⇔ f ⊥ = [](4.6)

head (mfix f )=fix (head · f )(4.7)

tail (mfix f )=mfix (tail · f )(4.8)

mfix (λx. f x : g x )=fix f : mfix g(4.9)

mfix (λx. f x ++ g x )=mfix f ++ mfix g(4.10)

See Appendix B.6.



Remark 4.3.2 From the first two equivalences in Proposition 4.3.1, we see that mfix fstructurally follows f ⊥. That is, if mfix f is ⊥ or [ ], then so is f ⊥, and vice-versa.
Similarly, mfix f is a cons-cell exactly when f ⊥ is. We see this correspondence over andover in monads that are based on sum-like data structures. (See also Remark 2.6.4.)
Proposition 4.3.3 Equation 4.4 defines the unique value recursion operator for the list
monad.

40

Proof

Strictness is exactly the first equivalence in Proposition 4.3.1. Purity is easy toestablish; we leave it to the reader. Left shrinking is more interesting. We show:
mfix (λx. a = λy. f x y) = a = λy. mfix (λx. f x y)
by structural induction on a. The base cases, a = ⊥ and a = [ ], are immediate. For the
inductive step, we assume a = q : qs, and reason as follows:
mfix (λx. (q : qs) = λy. f x y)
= mfix (λx. f x q ++ qs = λy. f x y)
= mfix (λx. f x q) ++ mfix (λx. qs = λy. f x y)

{Eqn. 4.10}= mfix (λx. f x q) ++ qs = λy. mfix (λx. f x y)

{I.H.}= (λy. mfix (λx. f x y)) q ++ qs = λy. mfix (λx. f x y)
= (q : qs) = λy. mfix (λx. f x y)establishing the left shrinking property, and completing the proof that we have a legitimate
value recursion operator.
For uniqueness, we will appeal to the approximation lemma. 2 Let mfix refer to thefunction defined by Equation 4.4, and let mfix0 be another value recursion operator for thelist monad. We will show that:∀n.∀f. approx n (mfix f ) = approx n (mfix 0 f )establishing uniqueness. The proof is by induction on n. The base case (n = 0) is
immediate. The induction hypothesis is:∀f. approx k (mfix f ) = approx k (mfix 0 f )for a fixed natural number k. We need to show that:∀f. approx (k +1) (mfix f ) = approx (k +1) (mfix 0 f )Pick an arbitrary function f. The proof proceeds by case analysis on the value of f ⊥.
If f ⊥ = ⊥, then both sides yield ⊥ by the strictness property. If f ⊥ = [ ], thenf = const [ ] by monotonicity, and both sides yield [ ] by Proposition 2.6.1. The casewhen f ⊥ is a cons-cell is a bit more involved. By monotonicity, we havef x = (head · f ) x : (tail · f ) x = [(head · f ) x ] ++ (tail · f ) x(4.11)

for all x, since f will always produce a cons-cell given any argument. Furthermore, thelist monad satisfies Equation 3.8, and hence mfix0 must satisfy Equation 3.10 by Proposition 3.4.2, where ⊕ = ++. Now, it is easy to see that:2

See Appendix B.6 for a more detailed example use of this lemma.

41

mfix 0 f = mfix 0 (λx. [(head · f ) x ] ++ (tail · f ) x )= mfix 0 (return · head · f ) ++ mfix 0 (tail · f )= return (fix (head · f )) ++ mfix 0 (tail · f )= [fix (head · f )] ++ mfix 0 (tail · f ){Eqn. 4.11}{Eqn. 3.10}{purity}= fix (head · f ) : mfix 0 (tail · f )Also note that Equation 4.4 will take its second branch when f ⊥ is a cons-cell. Therefore,
the proof obligation reduces to:head (fix (f · head )) : approx k (mfix (tail · f ))= fix (head · f ) : approx k (mfix 0 (tail · f ))by the definition of approx, and the above derivation. But this equation is immediate:
First elements are equivalent by the dinaturality of fix, and the tails are equivalent by the
induction hypothesis.
Properties It is not very hard to show that the sliding and nesting properties hold. By
the last equation in Proposition 4.3.1, distributivity holds as well (Property 3.4.1). On
the negative side, both strong sliding and right shrinking properties fail, as pointed out in
Corollary 3.1.7.
Remark 4.3.4 The maybe monad embeds into the list monad, as described in Section 3.5. Furthermore, the value recursion operator for the maybe monad is exactly theone predicted by Equation 3.14.
Remark 4.3.5 We close this section by discussing failing definitions of mfix for the list
monad. Consider the function:f xs = [take 3 (1 : xs), take 3 (2 : xs)]

(4.12)

What should mfix f be? Our definition yields: [[1, 1, 1], [2, 2, 2]], but the reader might
wonder about [[1, 1, 1], [2, 1, 1]], or [[1, 2, 2], [2, 2, 2]], which are produced by thetwo alternatives we have seen at the beginning of this section, i.e., by pivoting over the
first and second elements of the result. As we have mentioned, there is an infinite familyof such operators:3
mfix i f = fix (f · head · tail i ),3

i≥0(4.13)

Note that these alternatives do not form a chain; they are all incomparable. Furthermore, they are allincomparable to our definition of mfix (i.e., Equation 4.4) as well.

42

How about properties? It is easy to see that strictness holds for all mfix i , but that’s
where the good news ends. Except for mfix 0 , all members violate purity. We have:
mfix i (return · f ) = return (f ⊥),i>0

Furthermore, the left shrinking property fails for all of them. For instance,
mfix0 (λx. [1, 2] = λy. [y, x ])= [1, 1, 2, 1][1, 2] = λy. mfix0 (λx. [y, x ])= [1, 1, 2, 2]

compromising the equivalence of do and mdo expressions in the absence of recursion.
Intuitively, these definitions cause interference between elements. Note that:
λx. [1, 2] = λy. [y, x ] = λx. [1, x , 2, x ]and there is no reason to expect anything but ⊥ to play the role of x in the fixed-point,
as there is no information on what it can be. Indeed, our definition of mfix yields:
mfix (λx. [1, 2] = λy. [y, x ]) = [1, ⊥, 2, ⊥][1, 2] = λy. mfix (λx. [y, x ]) = [1, ⊥, 2, ⊥]In the light of this discussion, we see that neither the list [[1, 1, 1], [2, 1, 1]], nor thelist [[1, 2, 2], [2, 2, 2]] constitute a viable fixed-point for the function defined by Equation 4.12. Each indicate interference between the elements of the fixed-point, violating
the left shrinking property.
In Section 9.1, we will see an example use of value recursion on the list monad, providing
practical evidence for the definition given by Equation 4.4 being preferable over those given
by Equation 4.13.

4.4

State

State monads capture the notion of computations that depend on modifiable stores, providing safe access to imperative features [51, 52]. A typical state monad, manipulating aninternal state with type τ , has the following structure [7, 91]:
type ST τ α = τ → (α, τ )return x = λs. (x , s)f = g = λs. let (a, s 0 ) = f s
in g a s 0

43

The corresponding value recursion operator is given by:mfixω

:: (α → ST τ α) → ST τ αmfixω f = λs. let (a, s 0 ) = f a s
in (a, s 0 )(4.14)(The reason for the name will be clear in a moment.)
Remark 4.4.1 The following picture depicts the operation of the value recursion operator for the state monad, providing the intuition for the diagrams we have been using so
far (see also Remark 2.2.2):
avalue outfstate instate out

The monads we have considered up to now (i.e., identity, exceptions, and lists) enjoy
the property that they all have unique value recursion operators. Is this the case forthe state monad as well? Referring to the picture above, we see that the resulting statetransformer is required to return the fixed-point value in the value out line in order tosatisfy purity, but it is not clear how we should determine the final state, i.e., the value ofthe state out line. Equation 4.14 captures the case when state out is obtained by runningf on the fixed-point value and the current state. It is possible to consider an alternative
semantics, where the resulting state is determined without any regard to the value part,i.e., without any use of the fixed-point value. That is, a definition of the form:
mfix f = λs. let (a, ) = f a s in (a, π2 (f ⊥ s))(4.15)

with the following picture:value outffstate instate out

We might think of this operator as being strictly sequential in the state, i.e., it does
not make use of any “future” knowledge in determining what the final state should be.
There is a whole family of such operators, using approximations to the fixed-point value:
mfix i f = λs. let (a, ) = f a s in (a, pick i f s),i≥0(4.16)

44

where
picki f s = π2 (f ((λa. π1 (f a s))i ⊥) s)(4.17)For instance, the picture for pick2 is:fff

state instate out

Note that mfix0 is precisely the operator defined by Equation 4.15. By construction,
each picki is an approximation to the next, i.e., picki v picki+1 , implying mfixi v mfixi+1 .
Furthermore, it is easy to see that:mfixω =

∞
G

mfixi

(4.18)

i=0

where the mfixω on the left hand side is the operator defined by Equation 4.14.
Example 4.4.2 The functions mfixi , for all i, and mfixω will always agree on the value
part of the fixed-point. It is the final state that will be approximated by each mfix i , thelimit being delivered by mfixω . To demonstrate, consider the following function:f:: [Int] → ST [Int] [Int]f xs s = (1 : xs, xs)
We have:

i times}|{z
π2 (mfixi f [ ]) = 1 : 1 : . . . : 1 : ⊥As expected, π2 (mfixω f [ ]) yields the infinite list of 1’s. Notice how approximations arereflected in the final state. (In all cases π 1 (mfix f [ ]), i.e., the value part, will always be
the infinite list of 1’s.)
Proposition 4.4.3 The functions mfix i , for all i (Equation 4.16), and mfixω (Equation 4.14) are value recursion operators for the state monad.
Proof

For brevity, we will only consider mfix ω here. Proofs for mfixi are a bit moretedious, but equally easy. For strictness, we note that a function f of type α → ST τ α isstrict exactly when f ⊥α s = (⊥α , ⊥τ ) for all s. We have:mfixω f s = let (a, s 0 ) = f a s in (a, s 0 )= let a = fix (λa. π1 (f a s)) in (a, π2 (f a s))

45

Because the function λa. π1 (f a s) is strict, its fixed-point is ⊥. Therefore, mfix ω f s =(⊥, ⊥), establishing that mfix ω f is ⊥.4
For purity, we have:mfixω (return · f ) = λs. let (a, s 0 ) = (return · f ) a s in (a, s 0 )= λs. let (a, s 0 ) = (f a, s) in (a, s 0 )= λs. let a = fix (λa. f a) in (a, s)= λs. (fix f , s)
= return (fix f )For left shrinking, we need to show that:mfixω (λx. g = λy. f x y) = g = λy. mfixω (λx. f x y)
Simple symbolic manipulation reduces both sides to:
λs. let (a, s 0 ) = g s(a 0 , s 00 ) = f a 0 a s 0
in (a 0 , s 00 )completing the proof.

Remark 4.4.4 Abusing the terminology a bit, one might consider mfix ω as a lazy-inthe-state value recursion operator, while mfix 0 is strict. As we will see in Section 4.8
and in Chapter 8 in detail, the operation of mfix 0 is quite similar to the operation ofvalue recursion operators for stream processing and IO monads. It is hard to develop
a corresponding intuition for mfix i when i 6= 0. We do not know any applications thatmight benefit from them. Furthermore, they behave strangely with respect to the nesting
property, as we will see shortly.
Properties Having established that mfix i for all i, and mfixω are value recursion operators, we now take a look at other properties. It turns out that sliding (Property 2.4.1)
is satisfied by all of them, but nesting (Property 2.5.1) only holds for mfix0 and mfixω .
Strong sliding and right shrinking properties only hold for mfix ω .
Counterexample 4.4.5 Let us first consider nesting. Let4

We caution the reader about the use of true products. In case of lifted products, we would get
mfix f = λs. (⊥, ⊥) 6= λs. ⊥, violating strictness. But this is hardly surprising—even monad laws failin this case. It is easy to see that (λs. ⊥) = return = λs. (⊥, ⊥), failing the right unit law.

46f:: ([Int], [Int]) → ST [Int] [Int]f x s = (1 : π1 x , π2 x )Considering left and right hand sides of Equation 2.7, for each i > 0, we have:
π2 (mfixi (λx. mfixi (λy. f (x, y))) [ ]) = 1i+1 : ⊥π2 (mfixi (λx. f (x, x)) [ ]) = 1i : ⊥where 1k denotes a list of k 1’s. Since the final states differ, nesting fails. (The value partwill be the infinite list of 1’s in both cases.) For the single call to mfix i in the second line,
we simply get a snapshot of the value after i iterations, that is, exactly i 1’s. The nested
calls to mfixi , and hence to picki , result in the extra 1 in the first line. This behavior istruly bizarre from the viewpoint of value recursion. In case of mfix 0 , the final states willboth be ⊥, since the inner call to pick will be ignored by the outer one. In case of mfix ω ,the final state will be the infinite list of 1’s, as expected.
For strong sliding (Section 2.7.1), consider:f:: [Int] → ST [Int] [Int]hf xs s = (xs, xs):: [Int] → [Int]h xs = 1 : xs

Note that f (h ⊥) = λs.(1 : ⊥, 1 : ⊥) 6= λs.(⊥, ⊥) = f ⊥, hence sliding (Property 2.4.1)
does not apply. Considering Equation 2.5, we have:
π2 (mfix0 (map h · f ) [ ]) = ⊥π2 (map h (mfix0 (f · h)) [ ]) = 1 : ⊥showing that strong sliding fails. For right shrinking (Property 2.7.3), letf:: [Int] → ST [Int] [Int]f xs s = (1:xs, xs)g:: [Int] → ST [Int] [Int]g xs ( : k : ) = (xs, [k ])We leave it to the reader to show that right shrinking fails for mfix 0 with this instantiation.
It is possible to generalize these examples for all other mfix i , whenever i is finite.
Remark 4.4.6 We do not know whether there are other value recursion operators forthe state monad.

4.5

Output monad and monads based on monoids

Every monoid gives rise to a monad, referred to as its representation monad [2]. In programming, the best known example is the output monad, as we will see shortly. Let

47(M, ⊕, unit) be a monoid, where M is the underlying type. The corresponding representation monad is given by:
type RepM α = (α, M )return x = (x , unit)ma = f = let (a, m) = ma(b, n) = f ain (b, m ⊕ n)
For instance, substituting String for M , “” for unit, and ++ for ⊕, one obtains the usual
output monad [7, 91]. The obvious value recursion operator is given by:(α → RepM α) → RepM α

mfixω

::

mfixω f= let (a, m) = f a in (a, m)(4.19)

As with the state monad, the choice of the name mfix ω is not arbitrary. We have afamily of recursion operators:
mfix i f = let (a, ) = f a in (a, pick i f ),i≥0(4.20)

where
picki f = π2 (f ((π1 · f)i ⊥))(4.21)

A straightforward calculation (analogous to Equation 4.18) shows that:mfixω =

∞
G

mfixi

(4.22)

i=0

The correspondence with the state monad is not accidental. Any such representationmonad embeds into the state monad via the embedding: (a, m) = λn. (a, n ⊕ m)

(4.23)

with the left inverse: ` f = f unit. Furthermore,  works uniformly over all value
recursion operators, including mfix ω . That is, for any monoid M :RepM
 (mfixif) = mfixST( · f)i(4.24)

where i is either a natural number or ω. It is an easy exercise to show that the embedding
requirements (i.e., Equations 3.11-3.13) hold for .

48

Properties By Proposition 3.5.3, whenever an mfix for the state monad satisfies purity or left shrinking, the corresponding operator for the representation monad of a given
monoid will satisfy it too. Note that  is not strict, hence strictness is not automatically guaranteed (see Remark 3.5.4). However, it is easy to see that all mfix i and mfixω
satisfy strictness. Therefore, we have an infinite family of value recursion operators forrepresentation monads, similar to the case for the state monad.
By Proposition 3.5.5, sliding, nesting, strong sliding, and right shrinking properties
hold whenever the corresponding operator for the state monad satisfies them. On thenegative side, all of the counterexamples we gave for the state monad can be converted
to counterexamples for representation monads with no difficulty, invalidating nesting formfixi when i > 0, and strong sliding and right shrinking for all but mfix ω .
If the underlying monoid is idempotent, the representation monad will be idempotent as well. Similarly, commutativity of the monoid implies the commutativity of themonad. In both cases, mfixω will preserve idempotency and commutativity (Properties 3.2.1 and 3.3.1). Unfortunately, this result does not extend to mfix i automatically.5
Remark 4.5.1 Similar to the case for the state monad, it is an open question whether
there are other value recursion operators for monads based on monoids.

4.6

Environments

The environment monad, also known as the reader monad, captures computations thatuse a store to read values without modifying them. Using an environment of type ρ, theenvironment monad has the following structure:
type Env ρ α = ρ → α
return x = λe. xf = g = λe. g (f e) e
The corresponding value recursion operator is given by:mfix:: (α → Env ρ α) → Env ρ αmfix f = λe. let a = f a e(4.25)in a
5

For instance, Equation 3.4 will hold for mfix0 only when π2 (f ⊥) ⊕ π2 (f (π1 (f ⊥))) = π2 (f ⊥),which is not guaranteed just by the fact that ⊕ is idempotent. Similar arguments apply to Equations 3.5
and 3.7 as well.

49

Remark 4.6.1 It is an easy exercise to show that Equation 4.25 is equivalent to thegeneric mfix given in Section 1.4. To the best of our knowledge, identity and environment monads are the only examples where the generic version acts as the value recursion
operator.
Unsurprisingly, the environment monad embeds into the state monad. The embedding 6
is given by  f = λs. (f s, s), with the left inverse  ` f = π1 · f. It is easy to seethat strictness holds for Equation 4.25. Therefore, Proposition 3.5.3 guarantees that
Equation 4.25 defines a value recursion operator for the environment monad.
Properties By Proposition 3.5.5 and the observations made above, Equation 4.25 satisfies all the properties satisfied by mfix ω of the state monad. That is, sliding, nesting,strong sliding, and right shrinking properties, along with the basic requirements of strictness, purity and left shrinking are all satisfied.
Finally, the environment monad is both idempotent and commutative, and Properties 3.2.1 and 3.3.1 are both satisfied.
Remark 4.6.2 We do not know whether Equation 4.25 defines the unique value recursion operator for the environment monad.

4.7

Tree monadIn this section, we will briefly cover the tree monad [42]:data Tree α = Leaf α | Fork (Tree α) (Tree α)
return x= Leaf x
Leaf x
= f = f x
Fork l r = f = Fork (l = f ) (r = f )The effect of = is to splice new subtrees on every Leaf of the first argument. The
corresponding value recursion operator is given by:mfix:: (α → Tree α) → Tree α

mfix f = case fix (f · unL) of
Leaf x → Leaf x
Fork(4.26)→ Fork (mfix (lc · f )) (mfix (rc · f ))The functions unL, lc, and rc are defined as follows:6

It does not matter which mfix is chosen for the state monad (i.e., mfixω of Equation 4.14, or any mfixi
given by Equation 4.16).

50:: Tree α → αunLunL (Leaf x ) = x

lc, rc:: Tree α → Tree αlc (Fork l r ) = lrc (Fork l r ) = r

Compared to the value recursion operator for the list monad (Equation 4.4), we seethat unL plays the role of head, while tail is replaced by lc and rc, projecting out thechildren at each node. Otherwise, the definitions are structurally the same.
Remark 4.7.1 Despite all the similarities, the list monad does not embed into the tree
monad. There is no suitable element to map [ ] to, since our trees are always non-empty.
(An alternative formulation of trees, where data is stored in the nodes and leaves are
empty, does not give rise to a monad structure.)
Proposition 4.7.2 The function mfix given by Equation 4.26 satisfies:

Proofmfix f = ⊥⇔ f ⊥ = ⊥unL (mfix f )=fix (unL · f )(4.28)lc (mfix f )=mfix (lc · f )(4.29)

rc (mfix f )=mfix (rc · f )(4.30)

mfix (λx. Fork (f x ) (g x ))=Fork (mfix f ) (mfix g)(4.31)

Similar to the proof of Proposition 4.3.1.

(4.27)



Proposition 4.7.3 Equation 4.26 defines the unique value recursion operator for thetree monad.
Proof
Analogous to the proof for the list monad (Proposition 4.3.3). Note that weneed to use a different version of approx that works on trees [38] (see Appendix B.6). For
uniqueness, we cannot refer to distributivity, as the tree monad is not additive. (There is
no appropriate unit element.) However, we still have the operator: x ⊕ y = Fork x y,which satisfies:x ⊕ y = f = x = f ⊕ y = fhence a similar argument applies as in the case for the list monad. We leave the details
to the reader.
51

Properties Sliding and nesting properties can be shown to hold for the tree monad,
while strong sliding and right shrinking fails by Propositions 3.1.5 and 3.1.6.

4.8

Fudgets

In this section, we will take a look at fudgets, 7 a monad that has been designed to model
stream based computations. In its simplest form, the fudgets monad looks like:data Fudget α = Val α| Put Char (Fudget α)| Get (Char → Fudget α)
return = ValVal a
= f = f a
Put c m = f = Put c (m = f )Get h= f = Get (λc. h c = f )We will model functional I/O using a simple interpreter over this data type: 8run:: Fudget α → String → (String, α, String)run (Val a)inp= (“ ”, a, inp)run (Put c m) inp= let (o, a, r ) = run m inp in (’!’ : c : o, a, r )run (Get f )[]= error “trying to Get from an empty stream!”run (Get f )(c:cs) = let (o, a, r ) = run (f c) cs in (’?’ : c : o, a, r )The function run accepts a fudget and an input stream, runs the computation anddelivers the list of I/O operations that took place, together with the final value and theremainder of the input. The list of operations consists of all characters that are printed
via Put (prefixed by !), and all characters that are read from the input via Get (prefixedby ?). Note that the order is important, as it indicates the temporal relationship between
I/O actions. For instance, we have:run (Put ’a’ (Get (λc. Put c (Val c)))) “123” = (“!a?1!1”,’1’,“23”)For value recursion, we are interested in the meanings of fudgets of the form:
mfix (λxs. Put ’a’ (Val (1 : xs)))(4.32)

7
It would me more appropriate to call these “fudget-style stream processor monads,” as the presentation
here is only loosely based on the original work on fudgets by Carlsson and Hallgren [28, 29]. For brevity,
however, we will continue using the word fudget.
8
We will investigate Haskell’s internal IO monad in detail in Chapter 8.

52which intuitively models a computation that will print the character a and then deliver
an infinite list of 1’s. Or, more interestingly:
mfix (λcs. Get (λc. Val (c : cs)))(4.33)

which will first read a character from the input stream (if available), and then return an
infinite list containing copies of that character.
One possible value recursion operator for the fudgets monad is given by:
mfix f = case f ⊥ of
Val
Put c

→ fix (f · unVal )→ Put c (mfix (unPut · f ))Get

→ Get (λc. mfix (unGet c · f ))(4.34)

where
unVal (Val a)= a
unPut (Put m) = m
unGet c (Get h) = h c
With this definition, Expression 4.32 yields:run (mfix (λxs. Put ’a’ (Val (1 : xs)))) “z” = (“!a”, 1, “z”)
where 1̄ denotes the infinite list of 1’s. The result indicates that there was one I/O action,
which was printing the character ’a’, and no input was consumed. Expression 4.33 yields:run (mfix (λcs. Get (λc. Val (c : cs)))) “z” = (“?z”, ’z’, “ ”)
indicating that the character ’z’ is read from the input, the infinite list of z’s are returned,
and all of the input was consumed. If the input stream was empty to start with, we would
end up with the error case, i.e., the result would be undefined.
So far, the behavior of mfix seems to be consistent with the way we perceive I/O. Hereis a slightly more challenging expression:
mfix (λc. Put c (Val ’a’))(4.35)

What should the result be? Two possibilities arise. If we consider Put as an action causing
I/O, we see that it will not have its character ready for printing until after the computation
proceeds. That is, we should have:run (mfix (λc. Put c (Val ’a’))) “ ” = (“!⊥”, ’a’, “ ”)

53

leaving the printed character undefined. 9 Another option is to make the fixed-point value
available throughout the whole computation, yielding: 10
run (mfix (λc. Put c (Val ’a’))) “ ” = (“!a”, ’a’, “ ”)
However, this alternative behavior is quite questionable. Consider the expression:
mfix (λc. Put c (Get (λd. Val d )))In this case, we have to look past Get to determine what Put should print. However,this character is simply not available until we run this fudget with a particular input
stream. Such an operator would violate the temporal relationship between Put and Get.
(Furthermore, to achieve this effect, one would need to combine the operation of run andmfix, making the input stream available when the fixed-point is computed.)
Proposition 4.8.1 Equation 4.34 defines a value recursion operator for fudgets.
Proof

Strictness and purity are immediate. Left shrinking can be established byinduction. (As discussed briefly above, uniqueness is not guaranteed as we can speculate
on the character to be printed whenever we have a Put constructor.)
Properties Strong sliding and right shrinking both fail by Propositions 3.1.5 and 3.1.6.
Although we have not constructed the proofs, we believe that sliding and nesting properties
should hold.

4.9

Monad transformers

As pointed out in Section 3.6, monad transformers allow construction of new monads
from old ones. Although there is no magic recipe that will automatically lift a given mfix
through a transformer, it is possible to do so in many practical cases. In this section, we will
study three of the most common instances, namely error, environment, and state monadtransformers. (For a discussion of the continuation monad transformer, see Section 5.2.)
Liang defines the error monad transformer as follows [53]:9

The mfix we have given in Equation 4.34 produces this answer. As we will see in Chapter 8, the function
fixIO, the value recursion operator for Haskell’s IO monad, behaves similarly. (See Example 8.2.2.)
10
Ignoring the Get constructor, the fudgets monad is very similar to the output monad of Section 4.5.
The second alternative corresponds to the function mfixω (Eqn. 4.19), while the first one corresponds to
mfix0 (Eqn. 4.20). It is possible to think of operators that correspond to mfixi when i 6= 0 too.

54

data Err α= Ok α | Err String
type ErrT m α = m (Err α)
return a = return (Ok a)m = k = m = λa. case a of
Ok x → k x
Err s → return (Err s)
lift m = m = λa. return (Ok a)
Note that the return and = on the left hand side are the definitions for the newmonad Err m, while those on the right belong to the monad m. If m has a value recursion
operator mfixM, we can lift it up to Err m as follows:
mfixErrM:: (α → ErrT m α) → ErrT m α

mfixErrM f = mfixM (f · unErr )where unErr (Ok a) = a(4.36)The similarities between Equations 4.36 and 4.2 are not accidental. The function
unErr plays the same role as unJust, it providing access to the value part of the computation. While the value recursion operator for the maybe monad uses fix (i.e., the value
recursion operator for the identity monad) to tie the recursive knot, mfixErrM uses thevalue recursion operator for the underlying monad to do so.
Proposition 4.9.1 Let mfixM be a value recursion operator for a given monad m. The
function mfixErrM, defined by Equation 4.36, is a value recursion operator for the monad
ErrT m.
Proof

See Appendix B.7.

Let us now consider the environment monad transformer, which adds an immutable
store to arbitrary monads. The definitions for the environment monad transformer are [53]:type EnvT ρ m α = ρ → m α
return a = λe. return am = k = λe. m e = λa. k a elift m = λe. m
If the underlying monad has a value recursion operator mfixM, we can lift it to thetransformed monad as follows:
mfixEnvM:: (α → EnvT ρ m α) → EnvT ρ m α

mfixEnvM f = λe. mfixM (λa. f a e)

(4.37)

55

The definition of mfixEnvM exactly mimics the value recursion operator for the environment monad (Equation 4.25), just like the case for the error monad transformer andthe maybe monad. Analogous to Propisition 4.9.1, we have:
Proposition 4.9.2 Let mfixM be a value recursion operator for a given monad m. The
function mfixEnvM, defined by Equation 4.37, is a value recursion operator for the monad
EnvT ρ m.



Finally, we consider the state monad transformer [53]:type StateT σ m α = σ → m (α, σ)return a = λs. return (a, s)m = k = λs. m s = λ(a, s 0 ). k a s 0lift m = λs. m = λx. return (x , s)Applying the pattern we have seen with the previous two examples, a given mfixM canbe lifted through the state monad transformer as follows:
mfixStateM:: (α → StateT σ m α) → StateT σ m α

mfixStateM f = λs. mfixM (λr. f (π1 r ) s)

(4.38)

Proposition 4.9.3 Let mfixM be a value recursion operator for a given monad m. The
function mfixStateM, defined by Equation 4.38, is a value recursion operator for the monad
StateT σ m.

Remark 4.9.4 The lifting given by Equation 4.38 behaves analogously to mfixω as given
by Equation 4.14. It does not seem possible to lift arbitrary value recursion operators sothat they will behave similarly to any of the mfix i where i is finite (Equation 4.16).

4.10

Summary

In this chapter we have considered a wide range of monads and value recursion operators
for them. Although there is no magic recipe to automate the process, the examples provide
sufficient detail to guide the construction of value recursion operators for new monads.
There is one notable exception, however. The continuation monad does not seem topossess a value recursion operator. Chapter 5 contains the details.
We summarize the properties of value recursion operators we have studied in thischapter in the following table, along with the IO monad (studied in Chapter 8). The last

56

column indicates whether the corresponding value recursion operator is unique. A cellmarked with ∗ indicates a conjecture.
Str.

Pure

Left

Slide

Nest

S. Slide

Right

UniqueIdentity

X

X

X

X

X

X

X

X

Exceptions

X

X

X

X

X

✗

✗

X

Lists

X

X

X

X

X

✗

✗

X

mfix0

X

X

X

X

X

✗

✗

mfixi
mfixω

X
X

X
X

X
X

X
X

✗
X

✗
X

✗
X

mfix0

X

X

X

X

X

✗

✗

mfixi

X

X

X

X

✗

✗

✗

mfixω

X

X

X

X

X

X

X

Environment

X

X

X

X

X

X

X

X∗

Tree

X

X

X

X

X

✗

✗

X

X

X∗

X∗

✗

✗

✗

X

X∗

X∗

✗

✗

X∗

State

Monoids

Fudgets
IO

X
X

X
X

✗

✗

Let us conclude this chapter by making several observations about value recursion
operators:• We might hope that mfix constructs a fixed point value in the process of computation. Unfortunately, in general, we cannot expect to find a value z f such thatmfix f = f z f . Consider the function f xs = [1 : xs, 2 : xs]. There is no integer value zf such that f z f = [1 : 1 : ..., 2 : 2 : ...], which is the required resultin this case. Similarly, in the case of the state monad, the closest we can get is:
λs. f (fix (λa. π1 (f a s))) s, which shows that the state in which the recursive
computation gets performed is essential in determining the final result. Similarcomments apply to the expression mfix f = m f = f as well.
• Similarly, one might hope for a morphism suppress :: m α → m α, such thatmfix f = suppress (mfix f ) = fThe aim of suppress is to strip out effects. There are some monads for which such amorphism is available, but not in general. For instance, for the state monad:
suppress f = λs. let (a, ) = f s in (a, s)

57Intuitively, suppress can only exist when there is a clear structural separation between values and effects. For instance, such a separation seems impossible for themaybe or list monads• The equality mfix f  g = f ⊥  g does not hold in general. Since the value
produced by mfix f is discarded, one might think that the recursive computation
may be skipped as well. However, g might depend on the effects performed by the
first computation, which might very well be different for mfix f and f ⊥.
• It is worth reemphasizing some differences between fix and mfix. Recall that fix
satisfies the equality fix (f · f ) = fix f , for all f. However, it is not the case that:mfix f = mfix (λx. f x = f ), unless f is pure. In general, this equation only
holds when the underlying monad is idempotent (see Section 3.2). Similarly, theequation fix (f · h) = f (fix (h · f )) translates into
mfix (map h · f ) = map h (mfix (f · h))and requires f ⊥ = f (h ⊥) (see Section 2.4). Most importantly, the defining
equation for fix, fix f = f (fix f ), simply does not have any counterpart in thevalue recursion world. The unfolding view of recursion is not suitable for explaining
value recursion except for very mild effects (such as identity and environments), as
it does not distinguish between values and effects at all.

Chapter 5
Continuations and value recursion
Is there a value recursion operator for the continuation monad? Originally designed tomodel jumps, continuations come close to being the “universal” monad [24], and theirinteraction with recursion proves to be quite intricate. In this chapter, we will take acloser look at the structure of continuations from the viewpoint of value recursion.
Synopsis. We start with a review of the continuation monad, and continue by showingthat a value recursion operator for continuations is highly unlikely to exist. After a brief
discussion of the continuation monad transformer, we turn to first-class continuations,as found in Standard-ML and Scheme languages. We explore the interaction between
recursive binding constructs and first-class continuations, showing that the left shrinking
property is unattainable in such a setting.

5.1

A monad for continuations

Traditionally, continuation-passing style (CPS) has been used to model jumps in programming languages [90]. Continuations provide an extremely powerful effect, especiallyfirst-class continuations as supported by SML of New Jersey and Scheme [31, 44], hence
effective use of continuations require great care: As demonstrated by Thielecke, many
seemingly obvious equivalences fail to hold in the presence of a call-by-current-continuation
construct [84]. We will see a particular example related to recursion in Section 5.3.
Computations based on CPS can be described using monads. Wadler discusses monads
for continuations in a typed setting [90], while Espinosa’s thesis contains a discussion in
the untyped world [22]. A typical continuation monad has the following structure:
type Cont σ τ = (τ → σ) → σ
return x= λk. k xm = h = λk. m (λv. h v k)
58

59

The type variable σ encodes the result type. For any type σ, continuation-based computations with a result value of type of σ are modeled by the monad Cont σ. Other operations
on continuations include run, which provides an initial continuation; abort, which ignores
its continuation and immediately returns a result; and callcc, which enables saving thecurrent continuation for later use:run:: Cont σ σ → σ

run m = m id
callccabort:: σ → Cont σ σabort e = λk. e:: ((τ → Cont σ τ ) → Cont σ τ ) → Cont σ τcallcc h = λk. h (λv. (λc. k v )) k
It is worth noting that run takes continuations of type Cont σ σ, i.e., the argument andthe result types are the same. (Similarly, the result of abort is also restricted.) In callcc,
the function h is given a handle to the current continuation k. If h uses its first argument,
the control will be transferred to the point where callcc h was originally invoked. Notethat the inner argument, λc. k v, ignores its own continuation c, transferring the control
back to k. Otherwise h might ignore its first argument, proceeding normally.
Let us now turn to the question of value recursion for the continuation monad. Recall
that a value recursion operator has type (α → m α) → m α, where m is the underlying
monad. Expanding this type for continuations, we getmfix :: (α → (α → σ) → σ) → (α → σ) → σ(5.1)

where σ is the type of answers. Following the general pattern for value recursion, we
need to perform the fixed-point computation over α. However, it is simply not possible
to obtain a plausible value of type α by only using the arguments to mfix. Indeed, wewere not able to produce a plausible definition of mfix of even the correct type for thecontinuation monad, let alone a definition that would satisfy the required properties.
Let us explore the situation a bit more closely. Being explicit about the quantification,
we can rewrite Type 5.1 as:∀σ.∀α.(α → (α → σ) → σ) → (α → σ) → σ(5.2)

What are the inhabitants of this type? Fixing an answer type σ, we see that the Type 5.2
is isomorphic to:∀α.((α → σ) → α → σ) → (α → σ) → σ(5.3)

and it is not hard to see that this type is (infinitely) inhabited if we have a fixed-point

60

operator. Each one of the following cases forms a class of inhabitants:
f i (const v) ⊥α , i ≥ 0, v ∈ σmfix 0 f k = f i k ⊥α ,
i≥0



(fix f) ⊥
α

(5.4)

By v ∈ σ, we mean that v is an element of the domain that models the type σ. Each mfix 0
gives rise to an mfix via the equation mfix = mfix 0 · flip, and vice versa.1
We conjecture that the Equation set 5.4 completely covers all the inhabitants ofType 5.3. The proof attempt for such a claim would require an in-depth analysis of
the type, and is beyond the scope of the current work.
Conjecture 5.1.1 Let σ be an arbitrary type. Every inhabitant of Type 5.3 falls into
one of the categories given by Equation set 5.4.

Proposition 5.1.2 None of the candidate definitions for mfix 0 gives rise to an mfix thatwould satisfy the purity law.
Proof

We will only prove the casemfix 0 f k = f i k ⊥α ,i≥0Other cases are similar, if not simpler. Let α be a type and h be a function of type α → α.
By purity, we must have:
mfix (return · h) k = return (fix h) k = k (fix h)
Fix a natural number i. By the chosen definition of mfix 0 , we need:(flip (return · h))i k ⊥α = k (fix h)(5.5)(flip (return · h))i k = k · hi

(5.6)

It is easy to see that:

Substituting 5.6 in 5.5, we get:k (hi ⊥α ) = k (fix h)(5.7)

Obviously, Equation 5.7 does not hold for all k and h, given that i is a fixed natural
number.

1

The function flip is defined by the equation flip f x y = f y x.

61Remark 5.1.3 By the previous proposition, we conclude that the continuation monad(as defined in Section 5.1) does not possess a value recursion operator, provided Conjecture 5.1.1 holds.
The reader might wonder what happens if we restrict α to be the same as σ in theType 5.1, providing positive occurrences of α to work on. It is possible to show that there
is an infinite family of candidate mfix’s in this case as well, but none of them satisfy our
requirements. We leave the details to the interested reader.

5.2

The continuation monad transformer

The continuation monad transformer [53] is defined by:type ContT σ m α = (α → m σ) → m σ
return a = λk. k am = f = λk. m (λa. f a k )lift m = λk. m = kLet mfixM be a value recursion operator for a monad m. Can we lift it throughthe continuation monad transformer, obtaining a value recursion operator for the monad
ContT σ m? Following the recipe set forth by the examples of Section 4.9, we are led to
the following ill-typed definition:
mfixContM:: (α → ContT σ m α) → ContT σ m α

mfixContM f = λk. mfixM (λa. f a k )-- ill −typed !

(5.8)

Since the argument to mfixM has type α → m σ, the application is ill-typed. This failureis hardly surprising, as setting m to be the identity monad would have resulted in a value
recursion operator for the continuation monad.
Remark 5.2.1 Magnus Carlsson has suggested that such a lifting might be possible
when restricted to monads that support the notion of mutable variables (personal communication). In collaboration with Carlsson, we investigated a number of possible liftings,
but none of our attempts were satisfactory. In each case, it was fairly easy to show thatthe required properties were violated. We conjecture that a viable lifting is not possible
even in this restricted setting, leaving the exploration of this idea for future work.

62

5.3

First-class continuations and value recursion

What sort of properties can we expect from value recursion operators in a setting with
first-class continuations? First-class continuations allow programs to seize the control
state of their own evaluators [31]. This facility is definitely more powerful than what
the continuation monad of Section 5.1 provides, where programs can only manipulatecontinuations that are explicitly created and passed around by the programmer.
Many seemingly obvious equivalences fail to hold in the presence of first-class continuations. For instance, as shown by Thielecke, the equivalence (λx. False) (k True) = False
fails in the context callcc (λk. [ ]). (We refer the interested reader to Thielecke’s work formany other interesting examples [84].) When we consider the equivalences dictated by ourproperties, we see that they are simply too strong to hold in a language with first-classcontinuations as well. For instance, consider the left shrinking property (Section 2.3),
which states the following equivalence:
mfix (λx. a = λy. f x y) = a = λy. mfix (λx. f x y)
Recall that the computation represented by a does not use the recursion variable x (i.e.,
x is not free in a). However, in the presence of first class continuations, a can capture its
continuation via a call to callcc, thereby getting a handle on f which uses x. That is, acan indirectly access x through f, breaking the left shrinking property.
The following example in Scheme provides further insight into the problem. The
example demonstrates that a simple equality between recursive and non-recursive bindings(even simpler than our left shrinking law) fails to hold in the Scheme case. (This example
was brought to our attention by Amr Sabry, who traces it back to a message sent to thecomp.lang.scheme newsgroup in 1988 by A. Bawden, titled “letrec and callcc implement
references.”) Consider the following two Scheme expressions:(define (test1)(letrec ((x (call-with-current-continuation(lambda (c) (list #T c)))))(if (car x) ((cadr x) (list #F (lambda () x)))(eq? x ((cadr x))))))(define (test2)(let((x (call-with-current-continuation(lambda (c) (list #T c)))))(if (car x) ((cadr x) (list #F (lambda () x)))(eq? x ((cadr x))))))
Note that these two expressions are the same character for character, except the first
one uses the recursive binding construct (letrec) of Scheme, while the second one uses

63

the non-recursive version (let). Intuitively, these expressions should evaluate to the same
result, since the bound variable, x, is not even mentioned in the right hand sides of thebindings. Alas, these two expressions are not equivalent! When run, test1 evaluates to#T, i.e., True, while test2 yields #F, i.e., False. Regarding this example, Bawden wondered
if there were any “. . . real compilers that make this mistaken optimization,” regarding that
we might view test2 as an optimized version of test1. Of course, our concern is quite
the opposite. We rather ask if there are any language constructs that might render the
implied equivalence invalid.
Understanding why these expressions yield different values requires an understandingof how Scheme is interpreted. We will try to convey the idea here as it is essential inunderstanding why the left shrinking property is likely to be too strong a requirement in
the presence of first-class continuations. To keep the notation simple, let us rewrite these
expressions in a more Haskell-like syntax: 2
test1 ≡

test2 ≡

letrec x = callcc (λc. (True, c))in if fst x

let
x = callcc (λc. (True, c))in if fst xthen snd x (False, const x )then snd x (False, const x )else eq? x (snd x ())

else eq? x (snd x ())

Intuitively, letrec x = A in B in Scheme is implemented by allocating a cell called x
with a bogus error value, computing the value of the expression A (with x in scope), andthen overwriting the cell x with the result [44]. This allocate-compute-overwrite paradigm
practically achieves the knot-tying implementation of recursion. The evaluation then goeson with the expression B, again with x in scope. A simple let binding, on the other hand,does not create a cell to start with: let x = A in B is interpreted by evaluating A, storing
the result into a newly created cell x, and evaluating B with x in scope. With this modelin mind, consider the letrec expression in the definition of test1:
letrec x = callcc (λc. (True, c)) in . . .
To interpret this expression, one allocates a cell named x, and initializes it with ⊥. Then,the right hand side is interpreted. The crucial point is realizing what continuation is
captured by the call to callcc. Recalling our description above, the following continuation
will be captured:
1. Let a be the argument passed to the continuation. Overwrite the cell x with a,
2

The function eq? checks for pointer equality in Scheme, rather than structural equality.

64

2. Evaluate the expression part of letrec, i.e., evaluate:
if fst x then snd x (False, const x )else eq? x (snd x ())
Let us call the continuation described above κ. Now, the right hand side of the letrec
binding is computed, which returns the tuple (True, κ). Since the definition is not actually
recursive, the initial (undefined) value of x is not used. Then, the cell pointed to by x
is overwritten by this tuple and the interpreter continues on with the evaluation of the
body. Since fst x is True, the conditional takes its first branch. And it is exactly atthis point that we invoke the continuation through the expression snd x, which is passed
the argument (False, const x). Recalling the description of κ above, this tuple overwrites
the cell x. It is crucial to note the cyclic structure thus created: When called with anargument, the function stored in the second element of x will return a pointer back to thetuple itself. As dictated by step 2 of κ, we now evaluate the body with this new value
stored in the cell pointed to by x. But this time fst x is False, hence we end up evaluating
the expression eq? x (snd x ()). Since, snd x () returns a pointer back to x, the call to
eq? checks for the pointer equality of x and x, which simply results in the value True.
What happens with test2? Since we have a non-recursive let expression, the cell for
x is not created before the right hand side is computed. Let us call this continuation φ.
Here is our description of it:
1. Let a be the argument passed to the continuation. Store a in a new cell called x,
2. Evaluate the expression part of let, which is exactly the same as before.
To evaluate test2, we proceed by computing the right hand side of the let binding.
As before, we immediately get back the tuple (True, φ). Now a new cell named x is
created, which stores this tuple. The conditional again takes its first branch, and thecontinuation is called with (False, const x). Unlike the previous case, however, the call tothe continuation creates a new cell named x, shadowing the earlier value of x: The cyclic
structure is no longer available! It is not hard to see what happens now. The body isevaluated as before and the conditional takes its second branch. But this time we compare
two different tuples in the call to eq?. Hence the result is simply False.
The relevance of this example to the left shrinking property is obvious. Basically, theright hand side of the letrec binding, which is not recursive, corresponds to the constantcomputation in the left shrinking property. If left shrinking were to hold, we would beallowed to pull it out of the mfix loop, i.e., replace the recursive binding with a nonrecursive one. As we have seen, at least in the Scheme case, such a transformation is not
valid in the presence of first-class continuations.

65

5.4

Summary

As we have seen, the continuation monad in Haskell (as defined in Section 5.1) does
not seem to have a suitable value recursion operator. A similar comment applies to thecontinuation monad transformer. Furthermore, in the case of first-class continuations, theproperties we expect to hold for value recursion operators are simply too strong.
Regarding the handling of recursive definitions arbitrarily mixed with computational
effects in Scheme, Andrzej Filinski states (personal communication):...as far as I know, the only popular functional language that allows such definitions is Scheme; and I believe that allowing them was a mistake. The extra
generality is virtually never used, but it disallows some useful optimizations
by unnecessarily constraining the implementation. It is well known that in thepresence of call/cc, one can expose the imperative nature of letrec and use it
to define a general mutable cell; any RnRS-conforming system must support
this behavior no matter how it implements recursion...
We share the same point of view.

Chapter 6
Traces and value recursion
Trace operators were introduced into category theory by Joyal et al., as a means for modeling feedback operations arising in physics and mathematics [43]. Later work by Hasegawa
bridged the gap between recursion and traces, establishing a one-to-one correspondence
between fixed-point operators and traces over cartesian categories [9, 10, 32, 33, 79]. Can
we explain value recursion in this framework as well? The aim of this chapter is to review
the recent research in this area, trying to gain a better understanding of value recursion.
Synopsis. First, we will introduce parameterized value recursion operators, making thedependence on the environment explicit. After reviewing traced monoidal categories, wewill show that value recursion operators give rise to traces for a restricted class of monads.
Although the set of monads for which this is possible is quite small, the correspondence isstrong enough for us to explore. The restriction arises as a consequence of trace axioms,
which are simply too strong for value recursion in general. Motivated by this discussion,
we will briefly review recent work by Paterson [66], and Benton and Hyland [5], whichaims to generalize traces to premonoidal categories.

6.1

Parameterized value recursion

Recall that a value recursion operator for a monad m has type (α → m α) → m α. In a
categorical setting, one needs explicitly to account for terms that contain free variables,i.e., variables that are defined in the enclosing environment. To do so, we parameterize
our type to:((e, α) → m α) → (e → m α)
where e represents the environment. In the concrete case, e is generally a product, using
the cartesian structure of the underlying language. Parameterized and non-parameterized

66

67

value recursion operators are interdefinable:::

pmfixe,α f= λe. mfixα (λa. f (e, a))

mfixα

::

mfixα
where


((e, α) → m α) → (e → m α)

pmfixe,αf(6.1)(α → m α) → m α= pmfix ,α (f · π2 ) ()


(6.2)

is the terminal object whose only element is written (). The choice for the terminal

object is the natural one for e in Equation 6.2, as it represents the empty environment.
In fact, any type would do, since the environment is simply ignored.
Remark 6.1.1 Before proceeding further, a word on notation is in order. In this chapter,
we will be using a more categorical notation where appropriate. For instance, types will be
written with capital letters (as objects in a certain category), products will be written with×, etc. This shift is unfortunate, but necessary. We do not want to impose a Haskell-likenotation when talking about categorical constructs: Such a coercion seems to complicate
matters even more. As an example, the type for pmfix in 6.1 will be written:
pmfixA,X : D(A × X, T X) → D(A, T X)
where D is the category of domains and T is the underlying functor for the monad we areconsidering. (The notation D(A, B) denotes the set of arrows from A to B in D.) We
will stick to Latin letters for objects, following the general practice. The use of particularletters (i.e., X for the recursion variable, and A for the parameter) is inherited from
Hasegawa’s work [33]. Also, we will use categorical products and function spaces, rather
than Haskell’s lifted versions.
The second generalization we want to make is more technical than the first. Rather
than considering the morphisms in the base category, we move to the Kleisli category of
the given monad. There is one difficulty, however. The Kleisli category is not necessarily
cartesian. More specifically, the binary operator inherited from the cartesian product of
the base category is not necessarily bifunctorial. We will see the details and implicationsof this problem in Section 6.4.2. For the time being, let us just assume that we havea product-like operation in the Kleisli category, named ×. Let D T denote the Kleisli
category of a given strong1 monad T over D. It is easy to see that pmfix can be considered1

A monad over a category with a monoidal operation ⊗ is called strong if there exists a naturaltransformation tA,B : A ⊗ T B → T (A ⊗ B), called the strength, subject to certain conditions [63]. It turns
out that all Haskell monads are strong, with the strength defined as t (a, tb) = tb = λb. return (a, b).

68

as a family of functions with the type:pmfixA,X : DT (A × X, X) → DT (A, X)(6.3)

If DT is cartesian, Type 6.3 is precisely the same as that of a Conway operator (seeAppendix A). This view of value recursion will prove essential in the following discussion.

6.2

Preliminaries

In this section, we review the central notions in Joyal et al., and Hasegawa’s work [33, 43],
covering symmetric monoidal categories, traces, and the correspondence between traces
over cartesian categories and Conway operators.

6.2.1

Symmetric monoidal categories

In computer science, we often deal with binary operators that are associative only up toisomorphism. Monoidal operators and monoidal categories provide a setting where suchoperators can be modeled formally [2, 55]:
Definition 6.2.1 (Symmetric Monoidal Category.)

A symmetric monoidal category,

SMC for short, M = (M, ⊗, I, a, l, r, s) is a category M with a bifunctor ⊗ : M×M → M,
an object I ∈ M, and natural isomorphisms:
aA,B,C:A ⊗ (B ⊗ C) → (A ⊗ B) ⊗ ClA:

I ⊗A→ AsA,B:

A⊗B →B⊗ArA:

A⊗I → Asuch that the following diagrams commute:
Associativity Pentagon:A ⊗ (B ⊗ (C ⊗ D))a/ (A ⊗ B) ⊗ (C ⊗ D)a

/ ((A ⊗ B) ⊗ C) ⊗ D
O

A⊗a

a⊗D



A ⊗ ((B ⊗ C) ⊗ D)
Unit triangles and symmetry:A ⊗ (I ⊗ B)
MMM
MMA⊗l
MMM
a
MMM

&
/A⊗B
(A ⊗ I) ⊗ Br⊗Ba/ (A ⊗ (B ⊗ C)) ⊗ D

A ⊗ BIA ⊗ ID

II
IIA⊗B
II
s
II
I$

/
B⊗A
A⊗B
s

DD
DDr
DD
DD

!
/A
I ⊗A
s

l

69

Bilinearity:A ⊗ (B ⊗ C)a

/ (A ⊗ B) ⊗ Cs/ C ⊗ (A ⊗ B)
a

A⊗s



A ⊗ (C ⊗ B)a

/ (A ⊗ C) ⊗ Bs⊗B/ (C ⊗ A) ⊗ B

Example 6.2.2 All cartesian categories are symmetric monoidal. Let C = (C, ×, ) be
a cartesian category where × is the direct product with projections π 1 : A × B → A andπ2 : A × B → B. In this case, the natural isomorphisms of Definition 6.2.1 are given by:
a = hhπ1 , π1 · π2 i, π2 · π2 ia−1

= hπ1 · π1 , hπ2 · π1 , π2 ii

where !A : A →l = π2−1
lA = h!A , Air = π1s = hπ2 , π1 i

−1rA
= hA, !A i

s−1 = hπ2 , π1 idenotes the unique map to the terminal object. In Haskell notationthese morphisms correspond to the following functions (with more suggestive names):
assoc
left
right
swap

6.2.2

(x , (y, z )) = ((x , y), z )((), y)= y(x , ())= x(x , y)= (y, x )assoc−1 ((x , y), z ) = (x , (y, z ))left−1 y= ((), y)
−1
right
x
= (x , ())
swap −1 (y, x )= (x , y)Traced symmetric monoidal categories

Trace operators provide a categorical framework for studying cyclic structures: 2
Definition 6.2.3 (Traced SMC.) A traced symmetric monoidal category is a symmetricmonoidal category M = (M, ⊗, I, a, l, r, s) with a family of functions:
TrX
A,B : M(A ⊗ X, B ⊗ X) → M(A, B)
subject to the following conditions:• Naturality in A (left tightening):Ag

M(A ⊗ X, B ⊗ X)

TrX
A,B

O

M(g⊗X,B⊗X)



A0

M(A0 ⊗ X, B ⊗ X)

/ M(A, B)O
M(g,B)TrX
A0 ,B/ M(A0 , B)

For all f : A0 ⊗ X → B ⊗ X, g : A → A0 , Tr (f · (g ⊗ X)) = Tr f · g.
2

The original work on traces was presented in the slightly more general setting of braided monoidal
categories [43]. Following Hasegawa [33], we only consider symmetric monoidal categories here.

70• Naturality in B (right tightening):
TrX
A,B

M(A ⊗ X, B ⊗ X)

Bg

/ M(A, B)

M(A⊗X,g⊗X)

M(A,g)M(A ⊗ X, B 0 ⊗ X)

B0

TrX
A,B 0

/ M(A, B 0 )For all f : A ⊗ X → B ⊗ X, g : B → B 0 , Tr ((g ⊗ X) · f ) = g · Tr f.
• Dinaturality in X (sliding):
Xg

M(A ⊗ X 0 , B ⊗ X)

M(A⊗g,B⊗X)/ M(A ⊗ X, B ⊗ X)

TrX
A,B



M(A⊗X 0 ,B⊗g)

X0

M(A ⊗ X 0 , B ⊗ X 0 )X0/ M(A, B)

TrA,B

For all f : A ⊗ X 0 → B ⊗ X, g : X → X 0 , Tr (f · (A ⊗ g)) = Tr ((B ⊗ g) · f).
• Vanishing:– For all f : A → B, TrIA,B (r−1 · f · r) = f .
– For all f : A ⊗ (X ⊗ Y ) → B ⊗ (X ⊗ Y ),
Y
−1
X⊗Y
TrX
A,B (TrA⊗X,B⊗X (a · f · a )) = TrA,B f .

• Superposing: For all f : A ⊗ X → B ⊗ X,
−1
X
TrX
C⊗A,C⊗B (a · (C ⊗ f) · a ) = C ⊗ TrA,B f .

• Yanking: For all X, TrX
X,X (sX,X ) = X.
The graphical versions of these axioms are given in Figure 6.1 [33, 43]. It is worth
comparing these diagrams to those that we have given in Chapter 2 for mfix. The thicklines in the figures for mfix represent monadic actions, i.e., side-effects, changes in thestate, etc., while the corresponding lines in Figure 6.1 represent data flow. The fixedpoint argument (i.e., X) is not directly available to the outside world in the formulationof trace (although this limitation can be easily circumvented). In mfix, however, the result
is the fixed-point value together with monadic actions.

6.2.3

Traces and Conway operators

The following theorem of Hasegawa (also independently established by Hyland) states theconnection between traces and Conway operators (See Appendix A for a brief review of
Conway operators):

71

f

g

=

f

g

f

Left Tightening

g

f

=

f

g

Right Tightening
f

g

f

=

f

f

Sliding

f

=

g

=

f

Vanishing

=

f

=

s

Yanking

Superposing

Figure 6.1: Trace Axioms
Theorem 6.2.4 (Hasegawa, Hyland) A cartesian category is traced exactly when it
possesses a Conway operator.
Proof

See Theorem 7.1.1 of Hasegawa’s thesis [32].



The correspondence can be summarized as follows. Assuming we have a trace operator
Tr, we can define a Conway operator (·) † : C(A × X, X) → C(A, X) as follows:f † = TrX
A,X (∆X · f) : A → X(6.4)

Similarly, given a Conway operator (·) † , we can define the following trace operator:†
TrX
A,B f = π1 · f · hA, (π2 · f ) i : A → B(6.5)

Since Conway operators provide a generalization of fixed-point operators on domains,
traces on symmetric monoidal categories provide a firm categorical framework for studying
fixed-point operators.
Example 6.2.5 In the setting of domains and continuous functions, the unique least
fixed-point operator for a function f : A → A is given by:
fix f =

Gf i ⊥Aiwhich gives rise to the following Conway operator: Given f : A × X → X,f † a = fix (λx.f (a, x)) : A → X

72

And, by Equation 6.5, we obtain the following (unique) trace operator: Given f : A×X →B × X,TrX
A,B f = π1 · f · hA, λa. fix (λx. π2 (f (a, x)))i : A → B
In Haskell-like notation, this definition simply reads::: ((α, γ) → (β, γ)) → α → β

trace

(6.6)

trace f a = let (b, x ) = f (a, x )in bwhich clearly shows the intent: The recursive knot is tied over x, leaving a function oftype A → B as the residue.

6.3

Traces and value recursion

As we have seen in the preceding section, traces provide a natural framework for studying
fixed-point operators, and by virtue of Theorem 6.2.4, the usual notion of recursion canbe explained by traces over cartesian categories. Does the correspondence hold up when
we consider value recursion? It turns out that a close relationship can be establishedfor commutative monads whose Kleisli categories are traced, but the trace axioms are
simply too strong for the general case. Still, we will explore this limited correspondence
closely, as it will help us identify the problems that arise in the general case. We start byexamining two particular classes of monads: commutative monads and monads based oncommutative monoids.

6.3.1

Commutative monads and traces

Let T be a strong commutative monad over an SMC M = (M, ⊗, I, a, l, r, s) with thegiven strength t. We write η for the unit, and µ for the multiplier of T .

The monoidal

structure over M carries over to the Kleisli category of T , denoted M T , as follows:
MT = (MT , ⊗0 , I, η · a, η · l, η · r, η · s)
The monoidal operator ⊗ is lifted to M T as follows. On objects, ⊗0 is defined to be thesame as ⊗. On arrows, f ⊗0 g is defined to be the arrow Θ · (f ⊗ g) in M, whereΘ:

T A ⊗ T B → T (A ⊗ B)

Θ = µ · T t · t0
Recall that t0 is the dual of t, given by T s · t · s. Since T is commutative, the othercandidate for Θ, i.e., µ · T t0 · t, yields exactly the same arrow.

73

In the case of CCC’s, a trace operator on the Kleisli category of a commutative monadgives rise to a parameterized value recursion operator on the underlying category. To see
this, let C be a CCC, and T be a commutative monad over C. If C T is traced, we have afamily of functions:
00
TrXA,B : CT (A × X, B × X) → CT (A, B)

which implies the existence of the following family of functions in C:
TrX
A,B : C(A × X, T (B × X)) → C(A, T B)
Hence, a candidate parameterized value recursion operator can be defined by setting:pmfixE,A f = TrA
E,A (T 4A · f )(6.7)

where ∆ a = (a, a).
Example 6.3.1 The environment monad provides a nice example of obtaining a value
recursion operator from a trace. For a fixed object E in a CCC, the functor T A = E ⇒ A,
i.e., the exponentiation functor with the first argument fixed, gives rise to the environment
monad. For convenience, we will stick to the Haskell notation. The monad structure andthe strength are given by:
return a = λe. ajoin f= λe. f e et (a, f ) = λe. (a, f e)
It is easy to see that T is commutative. The Kleisli category is traced, and thecorresponding family of functions in the base category is given by:trace:: ((α, τ ) → (E → (β, τ ))) → α → (E → β)

trace f a = λe. let (b, x ) = f (a, x ) e

(6.8)

in b
Using Equations 6.7 and 6.2 we get:mfix:: (α → (E → α)) → E → α

mfix f = λe. let (b, x ) = (map (λz. (z , z )) · f · π 2 ) ((), x ) ein b
Recalling map f g = f · g for the environment monad, we can simplify this definition to:
mfix f = λe. let x = f x e
in x

74

which is precisely the value recursion operator that we have given in Section 4.6 for theenvironment monad.
Example 6.3.2 This example demonstrates that having a commutative monad is not
sufficient to guarantee the construction of a value recursion operator: The corresponding
Kleisli category should be traced as well. As an example, consider modeling exceptions in
Set by disjoint sums, using the endofunctor T A =+ A, where

is the terminal object.

In Haskell-like notation, the monad structure and the strength are given by:η a = inr aµ (inl ()) = inl ()µ (inr a) = at (a, inl ()) = inl ()t (a, inr b) = inr (a, b)

It is easy see that T gives rise to a commutative monad, and hence its Kleisli categoryX :is symmetric monoidal. If SetT is traced, then we should have a family of arrows T r A,B

SetT (A ⊗X, B ⊗X) → SetT (A, B), where ⊗ is the lifting of the cartesian product. Hence,
we must have a family of arrows Set(A × X,+ (B × X)) → Set(A,+ B). However,since the computation might fail, we do not have a way of getting an X to tie the recursive
knot. In this case, the Kleisli category does not seem to possess a trace.
The reader might appreciate the situation in Haskell. The exception monad is theusual Maybe monad, except the Haskell version is not commutative (due to the possibilityof non-termination). Ignoring the non-termination issue for a moment, we would need tofind a trace operator with the type:((α, τ ) → Maybe (β, τ )) → α → Maybe βHere, τ is the recursion argument, on which we need to tie the recursive knot. However,
the required trace operator just does not exist, since we are not guaranteed to get a τ toform the required recursive loop. (Recall that there is no such problem for value recursion
in our setting—see Section 4.2 for details.)

6.3.2

Monads arising from commutative monoids

In Section 4.5, we explored monads that arise from monoids. In this section, we willconcentrate on those monads that are obtained from commutative monoids, and see how
a trace operator in the underlying category can be used to obtain a value recursion operator
for the corresponding representation monad.
The usual definition of monoids on sets can be generalized to arbitrary monoidal
categories [55]. Let M = (M, ⊗, I, a, l, r, s) be a symmetric monoidal category. A monoid

75

in M is a triple M = (M, +, e) where M ∈ M, + : M ⊗ M → M , e : I → M , with theusual associativity and unit laws. (The monoid is commutative if +·s = +, i.e., if the orderof arguments to + does not matter.) For such a monoid, the endofunctor T A = M ⊗ A
gives rise to the following strong monad, known as M ’s representation monad [2]:ηA

= (e ⊗ A) · lA−1

(6.9)

µA

= (+ ⊗ A) · aM,M,A(6.10)−1= aM,A,B · (sA,M ⊗ B) · aA,M,B

tA,B(6.11)

If M is commutative, then T will be commutative as well.
After all this machinery, we can finally state our goal. Let M be a traced SMC, M
be a commutative monoid in M, whose representation monad is T . As we have seen, T
is commutative and hence its Kleisli category is symmetric monoidal. Furthermore, thetrace on M lifts into MT , i.e., MT is also traced. If TrX
A,B : M(A⊗X, B ⊗X) → M(A, B)
is the trace operator on M, the trace operator on M T is given by:
Tr0 X
A,B

:

Tr0 X
A,B f

= TrX
A,M ⊗B (a · f)

MT (A ⊗0 X, B ⊗0 X) → MT (A, B)(6.12)

Example 6.3.3 Consider the monoid: (N, +, 0), where N is the flat domain of natural
numbers, and + is addition. The corresponding functor is: T A = N × A. As outlined
above, the monad structure is given by (in Haskell):
return x= (0, x )join(m, (n, x )) = (m+n, x )t(x , (m, y)) = (m, (x , y))t0((m, x ), y) = (m, (x , y))Since map f (m, x ) = (m, f x ), we have:(join · map t 0 · t) ((m, x ), (n, y)) = (n+m, (x , y))(join · map t · t 0 ) ((m, x ), (n, y)) = (m+n, (x , y))Hence, the commutativity follows from the commutativity of +, as promised. Recall from
Example 6.2.5 that the trace on the underlying category is given by:trace f a = let (b, x ) = f (a, x ) in b
which, by Equation 6.12, can be treated as a trace operator on the Kleisli category of Twith the type: (A × X → N × (B × X)) → (A → N × B). More explicitly, we have (where
we use Integer to represent N):

76

tr 0
tr 0 f:: ((α, σ) → (Integer , (β, σ))) → (α → (Integer , β))a = let (b, x ) = (assoc · f ) (a, x ) in b

By Equation 6.7, we obtain the following parameterized value recursion operator:
pmfix f = λa. let (b, x ) = (assoc · map (λx. (x , x )) · f ) (a, x )in bwhich gives rise to the following value recursion operator by Equation 6.2:
mfix:: (α → (Integer , α)) → (Integer , α)mfix f = let (b, x ) = (assoc · map (λx. (x , x )) · f ) x in b
By expanding the definitions and simplifying, one obtains:mfix:: (α → (Integer , α)) → (Integer , α)mfix f = let (n, x ) = f x(6.13)in (n, x )which is precisely the value recursion operator we have given for monads based on monoids(Equation 4.19) in Section 4.5.
Remark 6.3.4 It is important to note that the commutativity of the monoid does notplay any role in establishing the requirements of value recursion, although it is essential
for constructing a trace. If the monoid is not commutative, the representation monad will
not be commutative either, failing to yield a monoidal structure on the Kleisli category. In
that case, one cannot even talk about the notion of trace, as Definition 6.2.3 only applies
to symmetric monoidal categories. We will return to this issue in Section 6.4.2.

6.3.3

The correspondence

We now turn to the correspondence between value recursion operators for commutative
monads and trace operators over Kleisli categories. Before doing so, we will need toconsider what trace axioms mean in the Kleisli category of a given monad. Let T be themonad under consideration. In this setting, the trace axioms read: 3
3
In these equations, we use the Haskell notation and try to name variables according to their types,
i.e., a variable named a is of type A. Note the use of shadowing in λ-bindings, where we reuse variablenames to stick to our convention. Compared to the original trace axioms, these versions are indeed very
ugly to look at, but they are much more intuitive from a programming perspective. Also, to save space,
we use η to abbreviate return.

77• Left tightening: For all f : A0 × X → T (B × X), g : A → T A0 ,Tr (λ(a, x ). g a = λa 0 . f (a 0 , x )) = λa. g a = Tr f

(6.14)• Right tightening: For all f : A × X → T (B × X), g : B → T B 0 ,Tr (λ(a, x ). f (a, x ) = λ(b, x ). g b = λb 0 . η (b 0 , x ))= λa. Tr f a = g

(6.15)• Sliding: For all f : A × X 0 → T (B × X), g : X → T X 0 ,Tr (λ(a, x ). g x = λx 0 . f (a, x 0 ))= Tr (λ(a, x 0 ). f (a, x 0 ) = λ(b, x ). g x = λx 0 . η (b, x 0 ))(6.16)• Vanishing: For all f : A → T B,
Tr (λ(a, ()). f a = λb. η (b, ())) = f(6.17)

and, for all f : A × (X × Y ) → T (B × (X × Y )),Tr (Tr (λ((a, x ), y). f (a, (x , y)) = λ(b, (x , y)). η ((b, x ), y)))= Tr f(6.18)• Superposing: For all f : A × X → T (B × X),
Tr (λ((c, a), x ). f (a, x ) = λ(b, x ). η ((c, b), x ))= λ(c, a). Tr f a = λb. η (c, b)(6.19)• Yanking:Tr (λ(x1 , x2 ). η (x2 , x1 )) = η(6.20)

After these preliminaries, we can finally state the main result of this chapter:
Proposition 6.3.5 Let D be the category of domains, and T be a commutative monadover D. Let mfix be a value recursion operator for T , further satisfying strong sliding,nesting, and right shrinking laws. Then, the family of functions
traceX
A,B:

trace f= λa. mfixB×X (λ(b, x). f (a, x)) = η · π1

D(A × X, T (B × X)) → D(A, T B)

will satisfy Equations 6.14-6.20, i.e., it will provide a trace operator for D T .

(6.21)

78

See Appendix B.8 for the full derivation. We try to summarize the correspon-Proofdence at a higher level here. Unsurprisingly, left and right tightenings depend on the left
and right shrinking properties of mfix respectively. Sliding requires the use of Proposition 3.3.2, which depends on the commutativity of the monad and strong sliding (of mfix).
The first vanishing rule depends on left shrinking and purity, the second one also uses
nesting. The superposing rule only needs pure right shrinking (which is guaranteed byright shrinking). Finally, yanking is a direct consequence of purity.

Remark 6.3.6 Ideally, we should also establish that a trace operator on the Kleisli
category of a commutative monad yields a value recursion operator, using a translation of
the form:(A → T A) → T A

mfixA

:

mfix f= TrA,A (λ( , a). f a = λa. η (a, a)) ()

But we will refrain from pursuing the correspondence in this direction for the following
reasons:• Our treatment of value recursion operators takes place in the setting of continuousfunctions over domains. On the other hand, trace operators are presented in the
abstract setting of monoidal categories, hence the assumptions for the underlying
structure are significantly weaker. For instance, it is not clear what our strictness
axiom (i.e., f ⊥α = ⊥T α iff mfixα f = ⊥T α ) would correspond to in this setting. 4• As we explored above, the correspondence of traces and value recursion is rather
limited. Very few monads are commutative, and even fewer have their Kleisli categories traced. What we should seek, then, is a notion of trace in the non-monoidal
case. In short, trace axioms are just too strong for value recursion.

6.4

Dropping the monoidal requirement

As we have seen in the preceding section, the trace-based categorical account of fixedpoint operators falls short of explaining value recursion for all but a very restricted setof monads. Is it possible to generalize the theory of traces so that we can accommodate
value recursion more satisfactorily? In this section, we will briefly review two recent
4Hasegawa suggests that it might be possible to study strictness via the notion of uniform trace operators. See Proposition 7.1.4 in Hasegawa’s thesis [33].

79

attempts in this direction. First, we will look at Paterson’s work, which lifts mfix to
the world of arrows [66]. Second, we will review Benton and Hyland’s work on traced
premonoidal categories [5]. It turns out that both attempts describe essentially the same
axiomatization, although presented in slightly different contexts. The general idea is to
move to premonoidal categories, effectively dropping the monoidal requirement. 5

6.4.1

Arrows and loop

Hughes introduced arrows as a generalization of monads, making the input-output flow
more explicit [36]. An arrow ⇒ is a binary type constructor equipped with:arr : (α → β) → (α ⇒ β)
≫ : (α ⇒ β) → (β ⇒ γ) → (α ⇒ γ)
first : (α ⇒ β) → (α × γ ⇒ β × γ)
Intuitively, α ⇒ β represents a computation that receives an input of type α, performs
a computation with possible side effects, and delivers a result of type β, corresponding
to what an imperative programmer might call a procedure. The morphism arr makes aprocedure out of a pure function, while ≫ runs two procedures in sequence, threading theresult of the first to the second. The function first lets information to be passed around forlater use, mainly used for storing results of intermediate computations. The morphisms
arr, ≫, and first are required to satisfy a number of laws, similar to monad laws.
Example 6.4.1 Arrows generalize monads in the following sense. For every monad m,
the type Kleisli m gives rise to an arrow, where:
type Kleisli m τ σ = τ → m σ
arr f= return · f

f ≫ g = λa. f a = gfirst f= λ(a, c). f a = λb. η (b, c)Paterson argues that Power and Thielecke’s Freyd categories are equivalent to Hughes’s
arrows [73]. (We will briefly review Freyd categories in Section 6.4.2.)
Is there a corresponding notion of value recursion for arrows? Paterson generalizes
mfix to arrows, introducing the following loop operator [66]:loop : (α × γ ⇒ β × γ) → α ⇒ β5(6.22)We mention in passing that Jeffrey also used the so-called partial traces (ordinary traces that are
restricted to be applied only to certain maps) to model flow graphs and recursion in programming languages [39]. We will not review his work here, however, as it is not directly related to value recursion.

80

Note the similarity between this type and the type of trace operators (Definition 6.2.3).
As expected, value recursion operators give rise to loop operators for the corresponding
Kleisli arrows. Given a value recursion operator mfix, Paterson defines:
loop f = map π1 · mfix · f 0
where f 0 x y = f (x , π2 y)(6.23)

which can be shown to be equivalent to the function we have given for obtaining a trace
operator from mfix (Equation 6.21).
Paterson generalizes the trace axioms of Section 6.2.2 for loop, and adds a law called
extension, similar to our purity property. As expected, he weakens the sliding axiom
so that the function moved over is of the form arr k for some function k, syntacticallyguaranteeing purity. Unlike our sliding property for mfix, however, Paterson does not
require a further guarding equation to regulate the behavior on ⊥ (i.e., the antecedent inEquation 2.5). Similarly, right tightening is postulated as an axiom as well. Therefore,
the failure of strong sliding or right shrinking properties for the underlying mfix will cause
the trace axioms to fail. Similar comments apply to arrows that are not derived frommonads as well. Paterson makes similar observations, although he does not weaken his
axiomatization to accommodate accordingly [66].

6.4.2

Traced premonoidal categories

Closely related to Paterson’s work is Benton and Hyland’s recent generalization of traces
to premonoidal categories [5]. As we have seen throughout this chapter, the crux ofthe problem lies in the monoidal requirement that comes with traces. Motivated bythis observation, Benton and Hyland generalize traces to premonoidal categories. If thecategory is indeed monoidal, their definition simply reduces to the usual definition of traces
over monoidal categories.
Let us review the problem with the monoidal requirement more formally. What happens when the monad is not commutative? Let C be symmetric monoidal, with ⊗ as
the monoidal operation. Let T be a strong monad over C, with strength t. We do notassume that T is commutative. Consider the Kleisli category of T , C T . For clarity, we
will use the symbol * to denote arrows in C T . The symmetry in C lifts into CT withno problems, i.e., CT is symmetric as well. For any fixed object A, we have the functor
A ⊗ – in CT , mapping a given object B to A ⊗ B and an arrow f : B * B 0 to thearrow t · (A ⊗ f) : A ⊗ B → T (A ⊗ B 0 ) in C, which corresponds to the required arrow
A ⊗ B * A ⊗ B 0 in CT . It is easy to see that – ⊗ A also yields a functor in C T . However,⊗ is not a bifunctor, unless T is commutative. To see this, let f : A * A 0 and g : B * B 0

81

be two arrows in CT . There are two ways of obtaining the arrow f ⊗ g : A ⊗ B * A 0 ⊗ B 0 ,as captured by the following Haskell expressions:
λ(a, b). f a = λa 0 . g b = λb 0 . return (a 0 , b 0 )λ(a, b). g b = λb 0 . f a = λa 0 . return (a 0 , b 0 )The first composition is denoted by n, i.e., f n g = A 0 ⊗ g · f ⊗ B. Similarly, we define
f o g = f ⊗ B 0 · A ⊗ g. Unless the monad is commutative, these two compositions
are generally different, as they reflect the order in which f and g are performed. This
discrepancy is the main reason why the monoidal structure in the base category does not
lift to a monoidal structure in the Kleisli category.
Of course, even when we only have a non-monoidal operation, there might exist a
subset of arrows for which the order does not matter—think of f (or g) having the form
return·h in the expressions above. Such arrows are called central. More formally, an arrowf is central if, for all g, f n g = f o g, and g n f = g o f .
Generalizing from this example, Power and Robinson introduced premonoidal categories [72]. In short, a premonoidal category is just like a monoidal category, except thebinary operation is only required to be functorial in each of the variables separately. (Notethat every monoidal category is trivially premonoidal.) As we have sketched above, Kleisli
categories of strong monads are examples of premonoidal categories.
Given a symmetric premonoidal category, can we come up with a notion of trace?
Recall that traces are only meaningful in symmetric monoidal categories. Naively, one
might hope that the definition of trace (Definition 6.2.3) might very well apply in thiscase as well. Unfortunately this is not the case:
Proposition 6.4.2 (Benton, Hyland [5]) A symmetric premonoidal category with a trace(Definition 6.2.3) is actually monoidal.

As expected, the sliding axiom causes the trouble. Benton and Hyland show thatsliding implies f n g = f o g for all arrows f and g, establishing that the category
is indeed monoidal. To remedy the situation, Benton and Hyland generalize traces tocentered symmetric premonoidal categories. A centered symmetric premonoidal category
is a premonoidal category K, with a distinguished monoidal center M, and an identityon-objects strict symmetric premonoidal functor J : M → K [72]. For our purposes, itsuffices to consider M as a subcategory of K, where all arrows in M are central.
Kleisli categories of strong monads over symmetric monoidal categories are classicalexamples of premonoidal categories. Let M be symmetric monoidal, and let T be a strong
monad over M. As we have seen above, M T is symmetric premonoidal. Recall that a
Kleisli category has the same objects as the base category. Let the functor J : M → M T

82

be defined as follows. On objects, J is the identity. Given an arrow f : A → B, let
J f = ηb · f : A * B, where η is the unit of T . Then J : M → M T is a centeredsymmetric premonoidal category, with the distinguished monoidal center M. (Of course,
J is nothing but the usual inclusion functor.) In this case, a central arrow in M T is simply
any arrow that is lifted from the monoidal center, i.e., any arrow that factors through ηin the base category.
The intuitive understanding of a centered symmetric premonoidal category J : M → K
is as follows: K is considered to be the category where arrows denote computations,
possibly with observable effects. As expected, K does not possess a monoidal structure.
M, on the other hand, is a subcategory of K denoting values, i.e., where we can swap
the order of computations, duplicate values only to discard later, etc. A crude analogy inprogramming terms is given by any “almost” functional language: For instance, think of
K as corresponding to the Standard-ML language, containing references etc., and M as
the purely functional subset of Standard-ML.
Getting back to traces, Benton and Hyland define [5]:Definition 6.4.3 (Traced centered symmetric premonoidal categories.) A trace on acentered symmetric premonoidal category J : M → K is a family of functions:
TrX
A,B : K(A ⊗ U, B ⊗ U ) → K(A, B)
satisfying the same conditions as given in Definition 6.2.3, except (i) the sliding condition is
weakened such that g is assumed central, and (ii) given a central arrow f : A⊗X → B ⊗X,
TrX
A,B f : A → B is required to be central.
It is easy to see that this definition generalizes the notion of trace, since all arrows arecentral in a symmetric monoidal category.
In order to generalize Theorem 6.2.4, Benton and Hyland also develop the notionof Conway operators on Freyd categories. Briefly, a Freyd category is a symmetric premonoidal category J : C → K, where C is cartesian [73]. A parameterized fixed point
operator on a Freyd category J : C → K is defined to be a family of functions(·)∗A,X : K(A ⊗ X, X) → K(A, X)(6.24)

Benton and Hyland require (·) ∗ to satisfy the so-called center preservation, naturality, andcentral fixed-point properties, corresponding to our left shrinking and purity laws.
To be able to establish a correspondence between traces over Freyd categories andparameterized fixed point operators, Benton and Hyland define Conway operators, whichfurther satisfy laws that correspond to our right shrinking and nesting properties. Hence,

83

similar to Paterson’s axiomatization of loop, the correspondence with premonoidal traces
only holds for the set of value recursion operators that further satisfy strong sliding andright shrinking properties. As we have seen in Section 3.1, these two properties are unsatisfiable for value recursion operators in general (Corollary 3.1.7).

6.5

Summary

In this chapter, we summarized the notion of traces from category theory, and investigated
how value recursion might fit into the picture. As we have seen, for a very small class ofmonads, value recursion operators correspond to trace operators over Kleisli categories.
The environment monad is the most important example exhibiting this correspondence(other than the obvious identity monad). In the general case, however, the correspondence
fails because of the monoidal requirement in the formalization of trace operators.
It turns out that Paterson’s loop axioms and Benton and Hyland’s generalization of
traces to premonoidal categories are essentially the same, although developed independently and presented in slightly different contexts [5, 66]. Both these axiomatizations takethe correspondence one step further, but not to the point where a satisfactory theory forvalue recursion can emerge. To summarize, both require right shrinking and strong sliding properties, which are known to be unsatisfiable for many monads (see Chapter 3). In
terms of concrete monads, their work can handle the lazy state and the output monads,
but not exceptions, lists, strict state, and the IO monad of Haskell. In this respect, weconsider both attempts to be significant steps in understanding and generalizing value
recursion, but not the final categorical account of the whole problem.

Chapter 7
A recursive do-notation
Haskell’s do-notation simplifies monadic programming significantly, but it lacks support forrecursive bindings, a key syntactic feature for value recursion. In this chapter, we describe
an enhanced translation schema for the do-notation and its integration into Haskell. 1
The new translation will allow variables to be bound recursively, provided the underlying
monad comes equipped with a value recursion operator.
Synopsis. We start with a motivating example, showing the need for recursive bindingsin the do-notation. The issues related to let-generators and the need for segmentation are
discussed next, followed by a detailed description of the translation algorithm. We also
provide several comments on the integration of the new do-notation into Haskell.

7.1

Introduction

Recursive declarations are ubiquitous in the functional paradigm. While fixed-point operators provide a solid framework for reasoning about and understanding recursion, they
are hardly suitable for practical programming tasks. For instance, compare:
let sum n = if n == 0 then 0 else n + sum (n − 1) in sum 10
to its non-recursive equivalent:
let sum = fix (λf.λn. if n == 0 then 0 else n + f (n − 1)) in sum 10
Clearly, the use of fix makes the definition much harder to read, especially for beginning
programmers. The situation gets worse with mutually recursive bindings.
As we have briefly mentioned in Section 1.3, a similar problem arises in the frameworkof value recursion. Rather than using explicit calls to mfix, we would like to have acomplementary binding construct, providing syntactic support for value recursion. In the1

The material in this chapter is based on a paper that appears in the Haskell Workshop’02 [19].

84

85

context of Haskell, an extension to the do-notation allowing recursive bindings seems to
fit the bill. To illustrate, we will revisit the circuit modeling example from Section 1.2.
This time, we will model a simple counter, one that increments its output by 1 at eachclock tick. The count goes back to 0 whenever the reset line goes high:
reset
zero

0

out

MUX

+1
next

DELAY 0inc

By extending the Circuit class (see Section 1.2) with multiplexers and monadic liftfunctions, we can model this circuit monadically as follows:counter:: Circuit m ⇒ Sig Bool → m (Sig Int)counter reset = mfix (λ˜ (next, inc, out, zero).
do next ← delay “zero” 0 inc
inc ← lift1 “add1” (+1) out
out ← mux reset zero next
zero ← lift0 “zero” 0
return (next, inc, out, zero))= λ(next, inc, out, zero). return out
As we have argued in Section 1.2, the monadic implementation has numerous advantages. Syntactically, however, it carries a lot of baggage, making it hard to understand andmaintain. (Note that binders can be arbitrary patterns in general, as in “Just x ← f x”,
making the situation even worse.) As pointed out by Launchbury et al. [49], and as wehave outlined in Section 1.3, what we need is a recursive counterpart of the do-notation,
allowing us to write simply [49]:counter reset = do next ← delay “zero” 0 inc
inc ← lift1 “add1” (+1) out
out ← mux reset zero next
zero ← lift0 “zero” 0
return out
eliminating the explicit call to mfix. Note that this description of the circuit follows thediagram given above almost literally. The translation we will introduce in this chapter
will handle such recursive definitions automatically, without bothering programmers withthe details of the necessary plumbing.

86

7.2

The basic translation and design guidelines

For clarity, we refer to the recursive version of the do-notation as the mdo-notation, andwrite mdo-expressions using the keyword mdo. 2 Whenever we refer to the do-notation,
we mean the currently available notation in Haskell that does not allow variables to bebound recursively.
Inspired by the counter circuit example of the previous section, one might naivelytranslate mdo-expressions as follows:
mdo p 1 ← e 1...
pn ← enmfix (λ˜BV. do p 1 ← e 1
..
=⇒

e

pn ← enreturn BV )
= λBV. e

where BV stands for the tuple consisting of all variables occurring in patterns p 1 . . . pn .
The lazy match, obtained by ˜, is essential in avoiding strictness problems.
However, there are a number of problems raised by the schema above. First of all,do-expressions in Haskell can use let-generators to introduce polymorphic bindings forpure expressions [68]. It is not clear how such bindings can be integrated into this translation. Similarly, ordinary do-expressions can bind identifiers repeatedly, later bindings
shadowing earlier ones. When bindings can be recursive, shadowing becomes problematic. Furthermore, the use of a single mfix to handle recursion over the entire body of an
mdo-expression may induce poor termination properties whenever the right-shrinking laws
fails (see Section 7.2.2)—intuitively, recursion should only be performed over generatorsthat depend on each other cyclically, leaving the rest untouched. Finally, we would liketo address these issues within the boundaries of the “syntactic-sugar” approach. That is,
the translation should produce only valid (well-formed and well-typed) Haskell code. Thisapproach keeps the extension simple, providing a smooth transition.
To summarize, the basic design guidelines for the mdo-notation are:• Syntactic agreement with the do-notation: Programmers familiar with the do-notation
should have no trouble using the recursive version.
• Semantic agreement with the do-notation: To the extent possible, valid do-expressions
should also be valid mdo-expressions, with their meanings preserved.
• Segmentation: Calls to mfix should be isolated to recursive segments only, leaving the
non-recursive parts out of the fixed-point computation. As we will see, segmentation
2

The closest we can get to µdo using ASCII.

87

is essential because extending the scope of recursion can give poorer results for thosemonads that fail to satisfy the right shrinking property.
• Pure syntactic sugar: The translation should only produce well-formed and welltyped Haskell code.
In the remainder of this section, we address these issues, refining the basic translation
scheme as we go along.

7.2.1

Let generators

The do-notation of Haskell allows let-generators, with the following translation [68]:do let p 1 = e 1...
pn = en=⇒stmts

let p 1 = e 1...
pn = enin do stmts

The variables bound in p1 . . . pn can be polymorphically typed. In mdo-expressions, thesevariables should be visible throughout the entire body as well, suggesting the translation:
mfix (λ˜BV. do stmts 1

mdo stmts 1

let p 1 = e 1...

let p 1 = e 1...
pn = en=⇒pn = enstmts 2
return BV )stmts 2e

= λBV. e

where the variables bound in p1 . . . pn will appear in BV as well. Unfortunately, theresulting code is not guaranteed to be well-typed. To illustrate, consider:mfix (λ˜ (z , y, f ).
mdo z ← f 2 yy ← f ’a’ z
let f x= return x

return (f y z , f z y)=⇒

do z ← f 2 yy ← f ’a’ z
let f x= return x

return (z , y, f ))= λ(z , y, f ). return (f y z , f z y)

Since f is λ-bound, it becomes monomorphically typed, making its use at two different
types illegal. In fact, the situation is even worse: Referring to the schematic translation
above, let-bound variables in patterns p 1 . . . pn will have monomorphic types over stmts 1

88

and e, while they will retain their polymorphic typings over stmts 2 and e1 . . . en . Thissituation is quite bizarre. Unfortunately, there is no easy solution to this problem. Since
the tuple BV is λ-bound, the variables that appear in it will be monomorphically typed
when we attempt to type check the body of the do-expression and the final expression e.
How should we deal with this problem? Clearly, it is unacceptable to ban let-generatorscompletely because they are quite useful in practice. (Requiring let-bound variables tobe visible only in the textually following generators would also be wrong.) An alternative
is to go slightly beyond Haskell 98, using records with polymorphically typed fields [40].
Rather than using tuples, we can package the arguments into a record with polymorphic
fields, retaining the polymorphic typings of let-bound variables. However, the resulting
translation is overly complicated (as we need to perform type inference during the translation), making it hard to formalize and automate [17]. One might also argue that we
can go beyond the “syntactic-sugar” approach, i.e., let the translation produce ill-typed
code, provided we can come up with special typing rules for mdo-expressions. We will not
pursue this option here, however, in order to be able to keep the translation as simple aspossible. (We will return to this point in Section 7.3.3.)
The solution we adopt is to require let bindings to be monomorphic in mdo-expressions.
That is, let becomes just a syntactic sugar within mdo, translated as:
let p 1 = e 1...

BV ← return (let p 1 = e 1...
pn = en=⇒pn = enin BV )

where BV is the tuple corresponding to the variables bound in p 1 . . . pn . This idea easily
extends to more complicated forms of function definitions as well. For instance:
mdo let f [ ]mdo f ← return (let f [ ]= 0

f (x :xs) = 1 + f xs
return (f [1,2,3], f [ ])

=⇒

= 0

f (x :xs) = 1 + f xs
in f )
return (f [1,2,3], f [ ])Note that we do not commit to a specific monomorphic type for f. As long as f is usedconsistently at a single monomorphic type, the translation will be well-typed.
We expect this restriction to be negligible in practice. Such polymorphic let-generators
are hardly ever used in practice, and experience suggests that there is almost always an
obvious way to rewrite the required polymorphic bindings using an explicit let-expression,
avoiding the whole problem. Therefore, we believe that the simplicity of this design far
outweighs any generality that might be obtained by more complicated translation schemas.

89Remark 7.2.1 It might help programmers if monomorphic bindings were visually distinguishable from polymorphic ones. In a recent paper, Hughes argues that the syntax
of let-expressions should be extended to allow monomorphic bindings, suggesting the useof the symbol := to differentiate them from polymorphic ones [34]. If this idea ever gets
adopted in Haskell, let-generators in mdo-expressions can be restricted to use := as well,emphasizing the fact that they will be monomorphically typed.

7.2.2

Segmentation

Consider the following mdo-expression, which creates two infinite lists consisting of 1’s
and 2’s respectively, and its translation:
mdo putStr “all 1s”
ones ← return (1 : ones)
putStr “all 2s”
twos ← return (2 : twos)
putStr “done”=⇒

mfix (λ˜ (ones, twos).
do putStr “all 1s”
ones ← return (1 : ones)
putStr “all 2s”
twos ← return (2 : twos)
return (ones, twos))
= λ(ones, twos). putStr “done”

The resulting code is quite unsatisfactory. The only recursion we need is in independently
computing the lists ones and twos, suggesting a segmented translation of the form:
do putStr “all 1s”
ones ← mdo ones ← return (1 : ones)
return ones
putStr “all 2s”
twos ← mdo twos ← return (2 : twos)
return twos
putStr “done”
where the inner mdo-expressions will further be translated accordingly. This process isanalogous to the handling of ordinary let-expressions in Haskell, where mutually dependent
bindings are grouped together to enhance types of bound variables [68]. In our case, allvariables are λ-bound, i.e., monomorphic, so typing is not an issue. However, we stillneed segmentation to avoid the unwanted interference from trailing computations. As an
example, letcheckSingle:: [Int] → IO ()checkSingle [x ] = putStr “singleton”= putStr “not−singleton”
checkSingle

90

and consider the following translation: 3
mdo xs ← return (1 : xs)
checkSingle xs
return ()=⇒fixIO (λxs. do xs ← return (1 : xs)
checkSingle xs
return xs)
= λxs. return ()Intuitively, we expect this mdo-expression to print “not-singleton”, as the value of xs
should simply be the infinite list of 1’s. Alas, the translation will diverge! The reason
is simply that the pattern matching in checkSingle is too strict for the computation toproceed, failing the match immediately. However, with segmentation, we will get the code:
do xs ← fixIO (λxs. return (1 : xs))
checkSingle xs
return ()which will happily print “not-singleton”, avoiding the unintended interference. Interestingly, if the final “return ()” is omitted, the original translation will work as well, since thecall to checkSingle will be the final expression, automatically pushed outside of the mfix
loop. Just adding “return ()” should not change the result, pointing out the need for segmentation. Note that this problem will arise whenever right shrinking fails (Section 2.7.2),
which is the case for many practical monads of interest. (See Corollary 3.1.7.)

7.2.3

Shadowing

The current syntax of do-expressions allows variable names to be bound repeatedly, laterbindings shadowing earlier ones. One can accommodate such bindings in the mdo-notation
as well, by appropriately renaming them. As a design choice, however, we reject this possibility. Although shadowing might be convenient at times, it is also a constant source ofbugs. Since bound variables are visible throughout the entire body in an mdo-expression,
allowing repetitions is much more likely to cause confusion. 4 Therefore, we disallow shadowing in mdo-expressions. (This design choice also implies that the scoping rules for
mdo-expressions are the same as those for let and where expressions, providing a consistent view of scoping in Haskell’s binding constructs, both pure and monadic.)
3

As we will see in Chapter 8, the library function fixIO :: (α → IO α) → IO α is the value recursion
operator for Haskell’s IO monad [20].
4
In a similar vein, it can be argued that repetitions should not have been allowed in the do-notation
either. List comprehensions become especially horrible: f x = [x | x ← [x .. x +5], x ← [x .. x +10]] isa confusing (yet legal) Haskell function.

91

7.3

Translation of mdo-expressions

We now present an algorithm to translate mdo-expressions to core Haskell.

7.3.1

Preliminaries

In the following discussion, we assume that let-generators are already de-sugared into theirreturn equivalents, as described in Section 7.2.1. We use the meta-variable p to range overpatterns, v over variables, and e over expressions.
Definition 7.3.1 (Defined variables.) A generator p ← e defines the variables thatappear in the pattern p. If the generator is of the form e, i.e., without any binding
patterns, then it defines no variables. An mdo-expression m defines a variable v, if v isdefined in a generator of m.
Definition 7.3.2 (Used variables.) A defined variable v is used in a generator p ← e if
v occurs free in e. (And similarly when there is no binding pattern p.)
Definition 7.3.3 (Recursive variables.) Let m be an mdo-expression, and v be a usedvariable of m. Let g be the generator that defines v. The variable v is recursive if it iseither used by g itself, or by a generator of m that appears textually before g.
Remark 7.3.4 Every defined variable comes from a distinct generator, due to the norepetition requirement. Furthermore, only defined variables can be used, and only usedvariables can be recursive. That is, for an arbitrary mdo-expression, we have:
Recursive Variables ⊆ Used Variables ⊆ Defined Variables
Definition 7.3.5 (Dependent generators.) A generator g is dependent on a textuallyfollowing generator g 0 , if
• g 0 defines a variable that is used by g,• or, g 0 textually appears in between g and g 00 , where g is dependent on g 00 .
Remark 7.3.6 The second condition in the above definition can be considered as interval
closure. Note that, unlike a usual let-expression, we cannot reorder the generators: Orderdoes matter in performing side effects. Hence, if a generator is dependent on another, weare forced to package them together with all the generators in between.

92

Definition 7.3.7 (Segments.) A segment of an mdo-expression is a minimal sequenceof generators such that no generator of the sequence depends on an outside generator. Asa special case, although it is not a generator, the final expression in an mdo-expression isconsidered to form a segment by itself.
Remark 7.3.8 To compute the segments, it suffices to start with the first generator of an
mdo-expression, and search for the last generator that it depends on. If such a generator
exists, we add all the generators up to and including it to the segment. This process isrepeated for each and every one of the generators in the segment, until we cannot add any
new generators. Once a segment is found, the very next generator starts a new segment.
Note that the number of segments is bounded above by the number of generators in the
mdo-expression, plus one for the segment corresponding to the final expression.
Definition 7.3.9 (Free variables of a segment.) Let m be an mdo-expression, v be adefined variable, and s be a segment of m. We say that v is free in s if (i) v appears
free in the right hand side of a generator of s, and (ii) v is defined in a segment textually
preceding s.
Definition 7.3.10 (Exported variables of a segment.) A variable that is defined in asegment is exported if it is free in any of the textually following segments.

7.3.2

The translation algorithm

We describe the algorithm step by step using the following schematic running example:
mdo {a b} ← {c d }

s0

{e}
{g}

← {f }
← {h}

s1
s2

{f }

← {a}

s3

{i j } ← {i e}{j g k}s4s5

where {v1 . . . vn } stands for a pattern that binds the variables v 1 . . . vn on the left hand sideof a generator, and for an expression whose free variables are v 1 . . . vn on the right handside. Note that the actual patterns or expressions are not important for our purposes. For
instance, the generator s3 uses the variable a, and defines f. Generator s 2 defines g, butdoes not use h, since h is not defined in this expression. For our purposes, it is nothing
but a constant. Similar remarks apply to the variables c, d, and k as well.

93

Segmentation step: Starting with the first generator, form the segments as describedin Remark 7.3.8.
To perform this step, we will need the defined (D i ) and used variables (Ui ) of eachgenerator si . Luckily, for our running example, these sets are obvious:
D0 = {a, b}
U0 = ∅

D1 = {e}U1 = {f}D2 = {g}U2 = ∅

D3 = {f}U3 = {a}D4 = {i, j}
U4 = {i, e}

D5 = ∅
U5 = {j, g}

To compute the segments, we start with s 0 . Since s0 does not use any variables, itcannot depend on other generators, i.e., it forms a segment by itself. The next generator
to consider is s1 , which uses the variable f. Since f is defined by s 3 , we have to package
everything in between, i.e., s1 , s2 , and s3 together. Since none of them depends on s4 or
s5 , we stop the iteration, forming our second segment. It is easy to see that s 4 and s5form the next two segments by themselves. Hence, we obtain:
S0 = {s0 }, S1 = {s1 , s2 , s3 }, S2 = {s4 }, S3 = {s5 }Analysis step: For each segment Si do the following: For each variable v defined in thesegment, determine whether it is recursive (Definition 7.3.3). Collect all recursive variablesof the segment Si in the set Ri . If Ri is empty, this segment does not need fixed-pointcomputation, leave it untouched. If R i is not empty, compute the exported variables ofthe segment, Ei , and mark this segment as recursive for future processing. Returning toour example, we have:
R0 = ∅

R1 = {f}, E1 = {e, g}

R2 = {i}, E2 = {j}R3 = ∅

Since only R1 and R2 are non-empty, we mark S1 and S2 as recursive; other segments
are left untouched. (Note that the last segment can never be recursive.)
Translation step: At this point, we are left with a number of segments, some of which
are marked recursive by the previous step. For each marked segment, create the tuples
ET and RT corresponding to the sets E and R. (If E is empty, ET will be the empty
tuple.) Create and add a brand new variable v to the tuple RT. Then, form the generator:ET ← mfix (λ˜RT. do .....
.....
v ← return ET
return RT )
= λRT. return v
where the dotted lines are filled with the generators of the segment.

94

Note that segments that are marked recursive by the previous step are turned intoa single generator, while non-recursive segments are left untouched. 5 Returning to our
example, we create the following generator for S 1 :(e, g) ← mfix (λ˜ (f , v). do {e} ← {f }{g} ← {h}{f } ← {a}v ← return (e, g)
return (f , v ))= λ(f , v). return v
and the following for S2 :j ← mfix (λ˜ (i, v). do {i j } ← {i e}v← return jreturn (i, v ))= λ(i, v ). return v
Finalization step: Now, concatenate all segments and form a single do-expression outof them. For our example, we obtain:
do {a b} ← {c d }(e, g) ← mfix (λ˜ (f , v ). do {e} ← {f }{g} ← {h}{f } ← {a}v← return (e, g)

return (f , v ))= λ(f , v). return v
j← mfix (λ˜ (i, v). do {i j } ← {i e}v← return jreturn (i, v ))= λ(i, v). return v{j g k }Remark 7.3.11 If there are no recursive bindings present to start with, the algorithm
we have described will just leave the input untouched (except for replacing the keyword
mdo by do). That is, the left shrinking property is automatically applied by the algorithm
to get rid of unnecessary calls to mfix. (See Section 2.3.)
5

Depending on the sets E and R, several other improvements are possible in forming the required
generator. For instance, if E is a subset of R, then we do not need a new variable. We skip a detailed
discussion of these improvements here, as they are not essential for the translation.

95

Desugaring step: Now we are left with a non-recursive do-expression, and we can apply
the standard translation to replace the do with explicit =’s, completing the translation [68].

7.3.3Type checking mdo-expressions

To accommodate for the overloading of the name mfix, we simply add the following typeclass to Haskell:class Monad m ⇒ MonadFix m where
mfix :: (α → m α) → m α
Intuitively, an mdo-expression is well-typed if its translation produces a well-typed
Haskell expression. In order to perform type-inference, a type judgement of the form:
Γ0 ` ei : m τi
Γ0 ` pi : τi
Γ0 ` e : m τΓ ` mdo {pi ← ei } e : m τ
suffices, with the side condition that m must belong to the MonadFix class. In this rule, Γ 0
is obtained by extending Γ with the variables defined in the given mdo-expression. Each
such variable is assigned a monomorphic type variable to begin with. (For simplicity, we
assume all generators have the form p ← e.) The only special care is needed in handling
let-generators, which can be typed similarly to normal let-expressions. To ensure that
let-bound variables are monomorphic, it suffices to leave out the generalization step in thetype inference algorithm for let-bound variables [17, 41].
As we have promised in Section 7.2.1, let us reconsider the typing of let-generators,
aiming to find a solution that would allow polymorphic bindings. In fact, it is arguablethat we should have a more liberal scheme, where normal bindings can be polymorphic as
well. For instance, there is no reason why the following expression should be ill-typed:
poly :: Maybe ([Bool], [Int])-- ill −typed

poly = do nil ← return [ ]return (True : nil , 1 : nil )However, poly is not a well-typed Haskell expression, since the binding to nil is required to be monomorphic. Of course, we cannot allow polymorphic typings arbitrarily,
as illustrated by the infamous ML-typing problem [93], coded here in Haskell:
do rf ← newSTRef (λx. x )writeSTRef rf (λx. x + 1)f

← readSTRef rf

return (f True)

96

Following the previous example, we might think that rf might be assigned the type∀α. STRef s (α → α), which leads to disaster. So, it seems that the maybe monad ismild enough that generalization is acceptable, but the state monad is not. It is beyond
the scope of our current work to investigate exactly when one might allow generalization,
but we conjecture that it is safe to do so in the following two cases:• For any variable, provided the underlying monad is completely definable in Haskell,and not built on top of one of the internal state or IO monads,• Or, variables bound by the let-generators, regardless of the underlying monad.
Since checking for the first condition seems to be rather expensive, we might settle
for allowing generalization in let-bound variables only, which coincides with the treatmentof let-generators in the current do-notation. (Such a solution would be similar to ML’svalue restriction, where only “syntactically distinguishable” values are typed polymorphically [93].) Of course, a more detailed study is needed before such an approach can be
adopted. We leave the exploration of this idea for future work.

7.4

Current status and related work

The mdo-notation is implemented both by the Hugs interpreter [37] and the GHC compiler [26]. Details on these implementations can be found on the web [74].
Predating our work, the need for recursive bindings in the do-notation was also discussed in the framework of Nordlander’s O’Haskell language, a concurrent, object-oriented
extension to Haskell [65]. O’Haskell extends the do-notation with a variety of new features.
With regard to recursion, O’Haskell provides a special keyword fix, providing a way to
specify a block of generators with mutual dependencies. The translation for fix-blocks is a
simpler version of ours: No segmentation is performed and let-generators are not allowed.
The translation seems to permit shadowing, but that appears to be an oversight, rather
than a conscious design decision. The addition of the fix keyword to the do-notation in
O’Haskell arose from practical programming needs; the syntax and the translation was
not designed to meet a general need.
Paterson’s arrow-notation supports recursive bindings as well, provided the underlying
arrow comes equipped with a loop operator [66]. (See Section 6.4.1 for a discussion ofarrows and loop operators.) Similar to O’Haskell, mutually dependent generators are
explicitly marked, using the keyword rec. No segmentation is performed on recursive
blocks. Currently, let-generators are not supported in the arrow-notation, but the additionof such bindings seems straightforward. We note that all variables become λ-bound after

97

the translation in the arrow-notation, forcing monomorphic types. Hence, regardless of the
support for recursive bindings, let-generators will suffer from the monomorphism problem
in the arrow-notation.

7.5

Summary

In this chapter, we have described an alternative translation schema for the do-notation of
Haskell, providing syntactic support for recursive bindings. The ability to bind variables
recursively in the do-notation is an essential feature for value recursion as it elegantly
hides the use of explicit value recursion operators.
Recalling the design goals we have set for the mdo-notation, we can conclude that
our translation fulfills its purpose. To review briefly, we have aimed for syntactic andsemantic agreement with the do-notation, segmentation for grouping minimally dependent
sequences of statements together, and preservation of the syntactic-sugar status. Ourtranslation achieves all these goals, except for syntactic agreement for a relatively small setof do-expressions. Since let-generators become monomorphic and shadowing is no longer
allowed, any do-expression using these features will be rejected. However, we believe thatneither of these restrictions will cause serious problems in practice. Also, if desired, thetyping problem might be remedied by devising a solution along the lines we have describedin Section 7.3.3.
It is our belief that Haskell should have just one version of the do-notation. Just likelet-expressions, do-expressions should be capable of expressing both recursive and nonrecursive bindings. (The type system will insist on the MonadFix instance only whenrecursive bindings are used.) However, such a change will potentially break existing programs, due to the minor incompatibilities mentioned above. Therefore, a separate notation(using the keyword mdo) has been adopted for the time being, possibly switching to the
new translation in a future version of the Haskell standard.

Chapter 8
The IO monad and fixIO
The IO monad of Haskell comes equipped with a value recursion operator, namely thefunction fixIO.1 Both the IO monad and fixIO are language primitives in Haskell, i.e.,they cannot be defined within the language itself. Therefore, any attempt to formallyreason about fixIO is futile without a viable semantics for computations in the IO monad.
Recently, Peyton Jones introduced an operational semantics based on observable transitions as a method for reasoning about I/O in Haskell [67]. In this chapter, we build on
his framework, and show how one can model fixIO as well. 2
Synopsis. We start with a brief discussion of the operation of fixIO, showing how itfits within the rest of the IO monad. We then describe a core language based on Haskell,with basic monadic I/O facilities. We continue by giving a layered semantics for thislanguage. Finally, we show that our model of fixIO satisfies the requirements for being avalue recursion operator with respect to our semantics.

8.1

Introduction

Ever since Peyton Jones and Wadler showed how monads can be used to model I/O ina language with non-strict semantics, monadic I/O became the standard way of dealing
with input and output in Haskell [69]. The IO monad in Haskell comes equipped with avalue recursion operator, namely the function fixIO. As Achten and Peyton Jones point
out, and as with all value recursion operators, fixIO “... allows us to manipulate results[of IO computations] that are not yet computed, but lazily available” [1, Section 4.1].
Unlike many other monads, the IO monad of Haskell is built into the language, as it
cannot be defined within Haskell itself. As a consequence, fixIO is a language primitive
1
The function fixIO is not part of the standard Haskell library [68]. Implementations, including Hugs
and GHC, provide it generally in the IOExts library.
2
This chapter is based on a paper that will appear in the Journal of Theoretical Informatics andApplications [21]. A preliminary version of the material presented in this chapter appeared in the Fixed
Points in Computer Science Workshop’2001 [20].

98

99

as well. Given we do not have direct access to the internals of the IO monad, how can
we understand the operation of fixIO? Or, in general, how can we understand IO-based
computations? Recently, Peyton Jones introduced a semantics for Haskell IO [67], similar
to the monadic transition systems of Gordon [27]. In such a system, IO computations
are viewed as sequences of labeled transitions. Each label indicates an effect observablein the real world, similar to those found in process calculi [61]. Peyton Jones’s work
used an embedding of a denotational semantics for the functional layer into the IO layer.
However, it bypassed the details of this embedding. Such an approach is fine, as long as
one is interested in the big picture. If, on the other hand, one wants to reason about fixIO,
it becomes necessary to be explicit about the relationship between the IO and functional
layers. One aim of this chapter is to bridge this gap.
Our semantics is structured in two layers: IO and functional. The semantics for the
IO layer is based on the approach taken by Peyton Jones [67]. The semantics for thefunctional layer is based on the natural semantics for lazy evaluation of Launchbury [48].
A final set of rules precisely shows how these two layers interact with each other. It is
this interaction that allows us to give a semantics for fixIO. (The material in this chapter
builds directly on Peyton Jones’s and Launchbury’s work mentioned above. We assume
that the reader is already familiar with these papers.)

8.2

Motivating examples

Although fixIO is just like any other value recursion operator we have seen so far, the factthat we cannot give a Haskell definition for it makes it rather mysterious. Also, the IO
monad provides mutable variables, a feature that we will have to deal with explicitly. We
start by considering several examples to get familiar with the operation of fixIO.
Example 8.2.1 Our first example shows the interaction of fixIO with input operations:
fixIO (λcs. do c ← getChar
return (c : cs))
When we run this computation, a character will be read from the standard input, saya. Then, the computation will immediately deliver an infinite list of a’s. 3 We will beable to pull out as many characters as we wish out of this list, following the demanddriven evaluation policy of Haskell. There are two crucial points: (i) the action getChar
3

Note that, by applying the left shrinking and purity properties, we can reduce this expression to
getChar = λc. return (fix (λcs. c:cs)), guaranteeing the described behavior axiomatically. Of course,
we have not yet established that these two properties hold for fixIO, but we will do so in Section 8.6.

100

is executed only once, and (ii) the computation terminates immediately after the reading
is done, i.e., the infinite list is not constructed prior to its demand. In other words, thefact that the IO monad is strict in actions but not in values is preserved by fixIO.
Here, we also get a feel for what fixIO provides: It provides a means for recursively
defining values resulting from IO computations. That is, it allows naming results ofcomputations that will only be available later on. For instance, in the expression above,
we were able to name the result of the computation as cs, before we had its value computed.
In this sense, the semantics is similar to the semantics of the pure expression:
let cs = ’a’ : cs in cs
which is a convenient way of writing fix (λcs. ’a’ : cs), where fix is the usual fixed-point
operator. Except, of course, in the fixIO case the character in the list is determined by thecall to getChar, i.e., it depends on the actual input available when we run the computation.
Example 8.2.2 Let us revisit the fudgets example given by Expression 4.35. In termsof fixIO, the corresponding computation is given by:
fixIO (λc. do putChar creturn ’a’)
When run, this computation diverges as c is not yet available when requested by putChar.
(Note that this behavior is in accordance with mfix as discussed in Section 4.8.)
Example 8.2.3 Here is a Haskell expression showing the interaction of fixIO with mutable variables:
fixIO (λ˜ (x , ). do y ← newIORef x
return (1:x , y))= λ( , l ). readIORef l
In this expression, we allocate a cell in which we store the value of the variable x, before
we know what that value really is. The value of x, determined through the fixed pointcomputation, is the infinite list of 1’s. The call to fixIO returns the value (which isdiscarded) and the address of the cell that stores this cyclic structure. Outside of the callto fixIO, we dereference the address and get back the lazily computed list of 1’s. Although
this example might look superficial, it basically captures the essence of cyclic structureswith mutable nodes. (See Section 9.4 for an example, where we use a similar idea toimplement doubly linked circular lists in Haskell.)Once we describe our semantics, we will revisit these examples to see how our system
works in practice.

101

8.3

The language

In this section, we define a language based on Haskell [68], supporting monadic IO primitives, mutable variables, usual recursive definitions, and value recursion.
Notation 8.3.1 We use the following naming conventions for variables:c∈constructors

x, y, z, w∈

heap variablesr, s, t∈mutable variables

To simplify the discussion, we syntactically distinguish between heap and mutable variables: They are drawn from different alphabets.
Definition 8.3.2 (Terms and values.)

Terms and values are defined mutually recur-

sively by the following grammars, respectively:

M, N

::=
|
|
|
|

x
V
MN
~ in Nlet ~x = M
case M of {ci ~xi → Ni }V ::= c x 1 x 2 ... x i| λx. M| return M | M = N
| getChar | putChar M| fixIO M | update z M| r| newIORef M| readIORef M| writeIORef M N

The function updatez , associated with the heap variable z, cannot appear in a valid
input program, and it is never the result of any program either. It is only used internally,in giving a semantics to fixIO. We will explain its role in detail later. All other constructs
have the same meaning and type as they do in Haskell [7]. Note that IO actions are values
as far as the purely functional world is concerned.
For the purposes of this chapter, we only work with well-typed terms, and ignore theissues of type checking and inference. We assume that the usual Haskell rules apply todetermine well typed terms. (Typing of Haskell programs has been discussed in detail inthe literature [41, 68].) Notice that return, =, fixIO, etc., are polymorphic constants.
As usual, let expressions provide recursive (and possibly polymorphic) bindings.
A constructor c of arity i is treated as a function λx 1 . . . xi . c x1 . . . xi , which becomesa value of its own when fully applied. This case is captured by the first alternative in thedefinition of values, where c is assumed to have arity i. We model constants as nullary
constructors, that is, numbers, characters, etc., are treated as constructors with zero arity.
(As a notational hint, we will use the letter k to refer to constants.)

102

Remark 8.3.3 It is worth noting that the grammar we gave describes the syntax forthe reduced terms of our language rather than the concrete syntax that we will allow
ourselves to use. In particular, we will freely use the do-notation and pattern bindings in
λ-abstractions. In each case, however, the translation to the core language will be trivial.
Definition 8.3.4 (IO and pure terms.) A well-typed term of type IO τ , for some type
τ , is called an IO term. All other terms are called pure.
Definition 8.3.5 (Terminal values.) A value is called terminal if it has one of thefollowing forms:• c x1 x2 . . . xi , where c is a constructor of arity i,• λx.M ,• return M ,where M is an arbitrary term in the second and third cases.
Definition 8.3.6 (Heaps.) A heap is a finite partial function from heap variables toterms extended with a special black hole value •:Γ :: Heap Variables * Terms ∪ {•}A heap binding can be polymorphically typed. A black hole binding, such as z 7→ •,indicates that the variable is known but not directly accessible. Intuitively, • is a detectablebottom.
Notation 8.3.7 Although heaps are functions, we will allow ourselves to use the set
notation freely on them: The notation x 7→ M ∈ Γ simply states that Γ maps x to M .
The empty heap is denoted {}. The notation (Γ, x 7→ M ) denotes the heap Γ extended
with a new binding x 7→ M . In this case, x cannot be already bound in Γ, but might
appear free in M .
Since our language allows input operations, the meaning of a term might depend on the
input stream it receives while being run. To accommodate this view, we have to consider
terms and input streams together.
Definition 8.3.8 (Input streams.)
sarily finite.

An input stream is a list of characters, not neces-

103

Notation 8.3.9 We will use the Haskell list notation to denote input streams. [ ] (or "")denotes the empty input stream, i.e., the case when the input is exhausted. Otherwise, astream is of the form (c : I), where c is a character and I is an input stream.
Definition 8.3.10 (Term and program states.) A running program is identified by its
program state, which consists of an input stream, a heap and a term state:(Terms States)

P

::=

M

Current term||

P | hxir

Passive container

νr.P

Restriction

We use the notation I : Γ : P to denote program states.
A term state is simply the current term under consideration, together with a numberof passive containers. A passive container hxi r represents a mutable variable named r,which holds a heap variable x. (We only store heap variables in these containers; theactual contents are stored in the heap.) Restrictions convey the scoping information formutable variables. Notice that a program state contains enough information to capture a
program in execution.
Remark 8.3.11 To reduce clutter, we will generally skip the bits of the program statethat are not needed in the discussion, especially when we write our rules. That is, wewill use Γ : P , if the input stream is irrelevant, and similarly I : P , when the heap is
not needed. There is no chance of confusion, however, because we only use capital Greek
letters for heaps and never skip the term state.
Definition 8.3.12 (The functions bn and fn.) The function bn takes a heap and returns
all the variables bound in it, i.e., bn(Γ) = {x | x 7→ M ∈ Γ}. The function fn is definedfor term states and heaps. Given a term state, fn returns the set of free variables in it. A
heap variable x is free if it is not in the scope of a λx binding. A mutable variable r isS
free if it is not in the scope of a νr binding. For a heap Γ, fn(Γ) = {fn(M ) | x 7→ M ∈Γ} − bn(Γ). We treat fn as a variable-arity function to simplify the notation: fn(A, B)
means fn(A) ∪ fn(B), and similarly for more arguments.
Definition 8.3.13 (Slice of a heap.)

The slice of a heap Γ, with respect to a termstate P , written Γ/P , is the subset of Γ that is reachable from the free names of P . Moreprecisely, for a given Γ and P , let
S0 = fn(P )Si+1 = Si ∪ ([{fn(M ) | x ∈ Si ∧ x 7→ M ∈ Γ})

104

and let S =

S

i∈


Si . Then,Γ/P = {x 7→ M | x ∈ S ∧ x 7→ M ∈ Γ}

(8.1)

Definition 8.3.14 (Closed program states.) A program state S : Γ : P is closed if
fn(Γ) = ∅, and fn(P ) ⊆ bn(Γ). (Note that if the second condition is satisfied, no mutablevariable in P can be free.)Definition 8.3.15 (Type of a program state.) Let S : Γ : P be a closed program state,
and let M be the term associated with P . We say that S : Γ : P has type τ , and write(S : Γ : P ) :: τ , when M has type τ when typed in the heap Γ.
Definition 8.3.16 (Terminal program state.) A program state S : Γ : P is terminal if
the term associated with P is terminal (Definition 8.3.5).

8.4

Semantics

We describe the semantics of our language in layers. The IO layer takes care of inputoutput and manages mutable variables. The functional layer handles pure computations.
A final set of rules regulate the interaction between these two layers.
Given a term, we need to be able to extract the part that is going to be executed next.
We use contexts to guide this search:
Definition 8.4.1 (Execution Contexts.)

Execution contexts are described by the fol-lowing grammar:(Execution Contexts)


::=
|

[·]


= M

An execution context is a term with one hole, where the hole itself is filled with a term.
The notation [M ] denotes the context filled with the term M . An empty context is
one where there are no =’s, as captured by the first alternative. Otherwise, the context


is non-empty, i.e., it is some IO action followed by others. 4 If the context is empty, theterm filling the context might be pure.

8.4.1

IO layer

Figure 8.1 gives the transition rules for the IO layer. A rule is a (possibly labeled) transition
from a program state to another. The label ‘!c’ indicates that the character c is printed
4

Other authors use the term evaluation context for this concept [23]. We prefer the term execution,
since a non-empty context can only be filled by an IO action which is going to be executed next.

105



−→

[return N = M ]−→[return () ](PUTC )

I : [return c ](GETC )

[M N ](LUNIT )

?c[getChar ](c : I) :


!c

−→

[putChar c ]





r∈
/ fn( [newIORef M ]) ∧ x ∈/ bn(Γ)Γ : [newIORef M ] −→ (Γ, x 7→ M ) : νr.( [return r ] | hxi r )(NEWIO )[readIORef r ] | hxir−→[return x ] | hxir

(READIO )y∈/ bn(Γ)(WRITEIO )Γ : [writeIORef r N ] | hxir −→ (Γ, y 7→ N ) : [return () ] | hyirz∈/ bn(Γ)Γ : [fixIO M ] −→ (Γ, z 7→ •) : [M z = update z ](FIXIO )(Γ, z 7→ •) : [update z M ]−→(Γ, z 7→ M ) : [return z ](UPDATE )Figure 8.1: Semantics: IO layer
on standard output, and the one labeled ‘?c’ indicates that the next character from the
input stream (which happens to be c) is consumed. 5
To simplify the notation, we use a couple of conventions in writing our rules (which
are going to be formalized in Section 8.4.4). Rather than a verbal explanation, we willconsider several illustrative examples:
Example 8.4.2 Consider the program state"ab" : Γ : getChar = putChar
for some heap Γ.

The term state consists of the single term getChar = putChar.

When we match this term to the context grammar given in Definition 8.4.1, we see that
there are two possibilities. Either we can have the empty context, filled with the term
getChar = putChar, or the context [·] = putChar, filled with the term getChar. Upon
inspection of our rules, we see that only the second has a chance of matching a rule,
namely GETC. Since the GETC rule requires the input stream to be of the form (c : I),
5

Note that this is the same convention as we have used for the execution of fudgets in Section 4.8.

106

we have to make sure that we have a non-empty stream. Because "ab" is not empty, the
GETC rule is applicable. Hence, we end up with the transition:"ab" : Γ : getChar = putChar?a−→ "b" : Γ : return ’a’ = putChar

Note that the GETC rule does not make use of the heap, hence it is not even mentioned.
The heap is simply carried across unchanged.
Example 8.4.3 Consider what happens when we continue the preceding example. Again,
there are two possible choices for the context. The empty context, filled with the term
return ’a’ = putChar, or the context [·] = putChar, filled with the term return ’a’.
Unlike the preceding case, however, the first choice matches the LUNIT rule, while thesecond one does not match any. Since the LUNIT rule does not constrain the input streamor the heap in any way, it is applicable. Hence, we end up with the transition:"b" : Γ : return ’a’ = putChar−→"b" : Γ : putChar ’a’

Since PUTC rule does not make use of the input stream or the heap, it does not explicitly
mention them. They are both simply copied. It should now be obvious that the nexttransition is:!a"b" : Γ : putChar ’a’ −→ "b" : Γ : return ()and there are no more transitions from this state, as none of the rules match.
Example 8.4.4 Consider the program state I : Γ : newIORef 5 = readIORef, for someI and Γ. The only matching choice for the context is [·] = readIORef, with the term
newIORef 5 filling the hole. The NEWIO rule applies. To satisfy the precondition of thisrule, we have to pick variables r and x such that r ∈/ fn(newIORef 5 = readIORef ) andx∈/ bn(Γ). We simply pick fresh variables to satisfy these requests. Let us call them r
and x for simplicity. We end up with the transition:
I : Γ : newIORef 5 = readIORef−→

I : (Γ, x 7→ 5) : νr.(return r = readIORef | hxi r )Example 8.4.5 We will continue with the previous example. Clearly, we want to apply
the LUNIT rule, but it is not clear how we get over the restriction νr. If we look at
the LUNIT rule, we see that only a term in context is specified (as in all rules except
READIO and WRITEIO). The convention we adopt in this case is the following: If a rule107

only mentions a term in a context in the term state position, then we consider the term
associated with the current program state and try to match it. Any remaining restrictions,passive containers, etc., are copied along. In this case, we obtain:
I : (Γ, x 7→ 5) : νr.(return r = readIORef | hxi r )−→I : (Γ, x 7→ 5) : νr.(readIORef r | hxi r )Example 8.4.6 Finally we show how to handle rules that have both a term in context
and a passive reference mentioned in their left hand sides, namely the WRITEIO andREADIO rules. Continuing the previous example, we see that the READIO rule needs
to be applied, which requires a term of the form readIORef r next to a passive container
named r. In this case, our convention is the following: If a rule mentions a term in context
next to a passive container, then a program state matches it if and only if we can showthat the term associated with it matches the term in context, and we are next to thecorresponding passive container. In our case, we get the following transition:
I : (Γ, x 7→ 5) : νr.(readIORef r | hxi r )−→I : (Γ, x 7→ 5) : νr.(return 5 | hxi r )Remark 8.4.7 The careful reader must have noticed that it is not necessarily the casethat we will always have the required passive container positioned nicely. For example, if
we start with the program state[ ] : {} : newIORef 0 = λr. newIORef 1 = λs. readIORef r
we will end up with:[ ] : {x 7→ 0, y 7→ 1} : νr.(νs.(readIORef r | hyi s ) | hxir )Clearly, we want to apply the READIOREF rule here as well. Alas, the rule does notmatch. In these cases, we will need to use structural rules, which provide means fortransforming the program state into an equivalent one such that there is an applicable
rule. Structural rules are covered in Section 8.4.4.
Some comments about the FIXIO rule are in order. The function fixIO is modeled
after knot tying recursion semantics. We first create a new heap variable, called z, whose
value is not yet known. This is achieved by binding it to •. Then, we call the function andpass it the argument z, and proceed normally. If the evaluation of this function needs to108

know the value of z, the derivation will get stuck with a detected black hole. Otherwise, z
could be passed around, stored in data structures, etc.: Note that it is just a normal heap
variable. Once the function call completes, we update the heap variable z by the result,effectively tying the knot by an application of the UPDATE rule. In summary, z holds
the value of the entire computation, which might in turn depend lazily on its own value,i.e., it is recursively defined.
Although the rules of our IO layer are quite similar to those given by Peyton Jones [67],the following differences are worth mentioning:• We keep track of the input stream explicitly, rather than assuming that standard
input will be consulted whenever a getChar is executed,• As in the natural semantics of Launchbury [48], we keep track of a separate global
heap to store values of variables,• Unlike Peyton Jones’s semantics, our reference cells only store heap variables, rather
than arbitrary terms. This restriction is necessary in order to model sharing impliedby lazy evaluation.

8.4.2

Functional layer

Our rules for the functional layer, given in Figure 8.2, follow Launchbury’s natural semantics for lazy evaluation closely [48]. Note that none of the rules in this layer mention the
input stream, as it is irrelevant at this layer. Also, we use the notation ⇓, rather than−→, for reductions. Compared to the IO layer, where we have a small step semantics, therules in the functional layer encode a big step natural semantics.
Γ:V ⇓ Γ:VΓ:M ⇓∆ : λy.M 0(VALUE )(∆, w 7→ N ) : M 0 [w/y] ⇓ Θ : V
Γ : MN ⇓ Θ : V(Γ, x 7→ •) : M ⇓ (∆, x 7→ •) : V(Γ, x 7→ M ) : x ⇓ (∆, x 7→ V ) : V(APP )(VAR )(Γ, x̂1 7→ M̂1 · · · x̂n 7→ M̂n ) : N̂ ⇓ ∆ : V
Γ : let x1 = M1 · · · xn = Mn in N ⇓ ∆ : V(LET )

Γ : M ⇓ ∆ : ck ~xk∆ : Mk [~xk /~yk ] ⇓ Θ : V
Γ : case M of {ci ~yi → Mi } ⇓ Θ : V(CASE )

Figure 8.2: Semantics: Functional layer

109

Compared to Launchbury’s natural semantics [48], some minor differences worth mentioning are:
• We introduce a new black hole binding,• The APP rule is generalized to application of terms to terms, rather than terms tojust variables. Correspondingly, we do not need to perform the normalization pass,• We perform renaming in the LET rule, rather than the VAR rule.
In the APP rule, we require w ∈/ bn(Γ). In the LET rule, we rename all bound variables
x1 . . . xn to x̂1 . . . x̂n so that there will not be any name clashes in the heap when we dothe additions. Similarly, the term M̂i denotes the term Mi , where each occurrence of xi
is replaced by x̂i . (Similarly for N̂ .) The VAR rule is not applicable if the variable being
looked up is bound to • in the heap. If this case ever occurs, the derivation will simply
terminate with failure, corresponding to a detectable black hole.
We refrain from going into details of this layer, as such systems are rather well studied
in the literature. The interested reader is referred to Launchbury’s original exposition [48],and Sestoft’s work on abstract machines based on such systems [78].

8.4.3

The marriage
Γ:M ⇓ ∆:k
Γ : [putChar M ] −→ ∆ : [putChar k ]

(PUTCEVAL )

Γ:M ⇓ ∆:r
Γ : [readIORef M ] −→ ∆ : [readIORef r ](READIOEVAL )

Γ:M ⇓ ∆:r
Γ : [writeIORef M N ] −→ ∆ : [writeIORef r N ](WRITEIOEVAL )

Γ:M ⇓ ∆:V
Γ : [M ] −→ ∆ : [V ]

(FUN )



Figure 8.3: Semantics: Marriage of layers. All these rules are subject to the side condition
that M is not a value.
Given separate semantics for the IO and functional layers, we need to specify exactly
how they interact. There are two different kinds of interaction. First, whenever we try
to reduce a term of the form, say, putChar M, we first need to consult the functional
layer to reduce the term M to a character. The IO layer will then perform the output.
(Note that the PUTC rule of the IO-layer only applies when the argument to putChar isa constant.) We need similar rules for readIORef and writeIORef as well. The first three

110

s∈
/ fn(P )Γ : νr.P ≡ Γ[s/r] : νs.P [s/r](ALPHA1 )y∈/ fn(Γ, M, P ) ∧ y ∈/ bn(Γ)(Γ, x 7→ M ) : P ≡ (Γ, y 7→ M )[x/y] : P [x/y](ALPHA2 )x∈/ bn(Γ) ∧ x ∈/ fn(Γ, P )(HEAPEXT )Γ : P ≡ (Γ, x 7→ M ) : P
P |Q
P | (Q | R)νr.νs.P≡≡≡

Q|P(P | Q) | R
νs.νr.Pr∈/ fn(Q, Γ/Q)
Γ : (νr.P ) | Q ≡ Γ : νr.(P | Q)

(COMM )(ASSOC )(SWAP )
(EXTRUDE )Figure 8.4: Semantics: Structural rules, Part I
rules in Figure 8.3 take care of this interaction. The second kind of interaction allows
handling of applications, let and case expressions, and variable lookups. This interaction
is provided by embedding the functional world into the IO world, as modeled by the FUN
rule. In all these rules, M is assumed to be a non-value: The functional layer is consulted
to reduce M to a value.

8.4.4

Structural rules

Finally, we need a set of structural rules to shape our derivations. As discussed in Remark 8.4.7, structural rules do not perform evaluation steps as do the other rules, butthey might be necessary in order to transform a program state to an equivalent one suchthat one of the transition rules can apply.
The first set of structural rules, presented in Figure 8.4, state that certain program
states are equivalent to others. As usual, we mention input streams and heaps only
when they are relevant. The ALPHA rules state that heap and mutable variables can berenamed at will, i.e., we do not distinguish program states that differ only in the namesof variables. (Substitution on heaps is defined as Γ[x/y] = {z 7→ M [x/y] | z 7→ M ∈ Γ}.)Note that we do not need a side condition of the form s 6∈ bn(Γ) in ALPHA1, since only
heap variables can be bound in the heap.
The HEAPEXT rule states that we can add new bindings, as long as they do notinterfere with existing bindings. See Section 8.6 for an example use of this rule.6 The rules
6

We can also add a garbage collection rule to get rid of unreachable heap variables and passive containers. We will avoid such a rule for the sake of brevity, as it is not essential for our current purposes.

111

α

α

P −→ QαΓ : P −→ Γ : Q(HEAPIN )P −→ QαP | R −→ Q | R
Γ : P ≡ ∆ : P0

(STREAMIN )αP −→ Qαα

I : P −→ I : Q(PAR )

α∆ : P 0 −→ Θ : Q0α

Γ : P −→ Σ : Q

P −→ Qανr.P −→ νr.Q

(NU )

Θ : Q0 ≡ Σ : Q(EQUIV )Figure 8.5: Semantics: Structural rules, Part II. The label α ranges over empty transitions
as well.
COMM, ASSOC and SWAP state obvious equivalences. Finally EXTRUDE shows how we
can manipulate the scoping of reference variables. The side condition in the EXTRUDE
rule guarantees that no dangling references will be created. (See Example 8.5.4 for details.)
The second set of structural rules, presented in Figure 8.5, formalize our conventions inapplying the rules. The first four rules simply state that we can concentrate on the relevant
bits of the derivation and add the extra bits later on. And finally, EQUIV states that weonly need to consider program states up to equivalence when performing transitions.
Example 8.4.8 We will reconsider the example discussed in Remark 8.4.7. Recall that
we had the program state:[ ] : {x 7→ 0, y 7→ 1} : νr.(νs.(readIORef r | hyi s ) | hxir )By applying EXTRUDE, ASSOC, COMM, ASSOC and READIOREF rules (and by appropriate applications of the rules in Figure 8.5 to enable them), we get:≡[ ] : {x 7→ 0, y 7→ 1} : νr.(νs.((readIORef r | hyi s ) | hxir ))≡[ ] : {x 7→ 0, y 7→ 1} : νr.(νs.((readIORef r | hxi r ) | hyis ))−→[ ] : {x 7→ 0, y 7→ 1} : νr.(νs.((return x | hxi r ) | hyis ))There are no matching rules for the resulting program state. We can apply structural
rules again, but none will give us a program state where a non-structural rule can apply.
Remark 8.4.9 One can extend ≡ to an equivalence relation on program states, simply
by adding rules to make it reflexive and transitive. However, the current definition of ≡given in Figure 8.4 is simply too crude to be useful for this purpose. Intuitively, we want to112

be able to identify program states if their “observable behavior” are the same [27, 57, 71].
We leave the exploration of this idea for future work.

8.4.5

Meaning of program states

The meaning of a closed program state is its derivation:
Definition 8.4.10 (Derivations.)

Let I : Γ : P be a closed program state. The

derivation for I : Γ : P is a sequence of labeled transitions, where at each step a rule isapplied. Structural rules can be applied at any time, as long as they trigger the applicationof a non-structural rule. The derivation continues until there are no applicable rules.
Simple inspection of our rules reveals that we have a deterministic system modulo thestructural rules. That is, given a program state there is at most one non-structural rulethat can apply to it.
Definition 8.4.11 (Effect of a derivation.) The effect of a derivation is the concatenation of its transition labels. Empty transitions do not contribute to the effect.
The effect of a program state is simply a (possibly infinite) list, where each element isof the form ‘?c’ or ‘!c’ for some character c.
Notation 8.4.12 As usual, −→∗ is the reflexive transitive closure of −→. We will shorten
αmultiple steps of derivations using the notation I : Γ : P −→∗ I 0 : Γ0 : P 0 .
Definition 8.4.13 (Divergent and normal program states.)

A closed program stateI : Γ : P is called divergent if the derivation starting from I : Γ : P either• continues indefinitely (i.e., we never run out of non-structural rules to apply),• or, gets stuck in a non-terminal program state (Definition 8.3.16) where no nonstructural rule applies.
Otherwise, I : Γ : P is called normal.
Example 8.4.14 It is easy to come up with divergent terms. For instance, one can showthat the derivation for:I : Γ : let loop = putChar ’a’  loop in loop

(8.2)

113

diverges, since we never run out of rules to apply. However, the derivation for:I : Γ : let x = x in x

(8.3)

will diverge by getting stuck. The FUN rule will never fire, because there are no reductions
for this term in the functional layer. (Notice that the first application of the VAR rule
will result in I : (Γ, x 7→ •) : x, but no other rule will apply since the VAR rule is only
applicable when the binding is not a black hole.) Similarly, a derivation can get stuck via
the use of the FIXIO rule (which introduces a black hole binding in the heap). A final
possibility is the application of the GETC rule when the input stream is empty.
Lemma 8.4.15 (Derivations for normal program states.) Let I : Γ : P be a normal
program state. The derivation starting at this state will take the formα

I : Γ : P −→∗ I 0 : ∆ : Q
where I 0 is a suffix of I. Furthermore, Q can be transformed using only the structural
rules to the form ν~r.(N | C), where N is a terminal value (Definition 8.3.5), and C is anumber (possibly zero) of parallel passive containers. The restrictions encoded by ~r cover
all passive containers in C.
Proof (Sketch.) By definition 8.4.13, our proof obligation reduces to establishing that
Q can be transformed into the required ν~r.(N | C) form. By inspection of the structural
rules, we see that the rule EXTRUDE can be repeatedly used to move restrictions to thetop, obtaining the required form. (ALPHA rules can be used to resolve naming conflicts,
if any.) To see the correspondence between restrictions and the passive containers, justnotice that they are introduced together by NEWIO, they are never removed, and all rules
respect the scoping of ν bindings.

Observation 8.4.16 Note that derivations apply to both pure and IO terms. A derivation either diverges, or ends up with an abstraction or a saturated constructor application
for a pure term, or with a term of the form return M for an IO term.
Proposition 8.4.17 (Derivations for IO terms in contexts.) Let I : Γ : ν~r.( [M ] | C)

be a closed program state, where M is an IO term. The derivation starting at this state
will either diverge, or take the form:α

I : Γ : ν~r.( [M ] | C) −→∗


β

−→∗

I 0 : ∆ : ν r~0 .( [return N ] | C 0 )

I 00 : Θ : ν r~00 .(return O | C 00 )where I 0 is a suffix of I, and I 00 is a suffix of I 0 .

114

Proof

By inspection of our rules, we see that if the derivation for Γ : ν~r.( [M ] | C)terminates, then so must the derivation for Γ : ν~r.(M | C). Hence, by the previous
lemma, it must do so in the required intermediate form. The form of the final state isagain guaranteed by the previous lemma.

To be able to talk about strictness (Equation 2.1), we need to say what ⊥ means forthe type IO τ :Definition 8.4.18 (Silent derivations.) A derivation is silent if its effect is empty.
Definition 8.4.19 (Bottoms of IO.) A closed program state (I : Γ : M ) :: IO τ is abottom element (⊥) for the type IO τ , iff the derivation for I : Γ : M silently diverges.
Example 8.4.20 It is easy to see that Program State 8.2 is not a ⊥ of IO, but Program
State 8.3 is. While they both diverge, the former is not silent.
Definition 8.4.21 (Strict functions.) Let Γ be a heap and M be a term such that the
program state ([ ] : Γ : M ) :: τ → IO σ is closed. M is strict, if, for all I and ∆ ⊇ Γ/M ,x∈/ bn(Γ), the derivation for
I : (∆, x 7→ •) : M x
is silently divergent.

8.5

ExamplesWe revisit the examples given in Section 8.2, and show how our semantics can handle
them. In these examples, we will use the letters a, b, . . . to represent heap variables aswell. To save space, we will apply the structural rules silently.
Example 8.5.1 We will revisit Example 8.2.1. We first remove the do notation in favorof explicit =’s:fixIO (λcs. getChar = λc. return (c : cs))
To reduce clutter, we will not write the input stream explicitly. We have:{} : fixIO (λcs. getChar = λc. return (c : cs))−→∗ (FIXIO - FUN){z 7→ •, a 7→ z} : getChar = λc. return (c : a) = update z?ch−→ (GETC – assume input stream has ch in front)

115{z 7→ •, a 7→ z} : return ch = λc. return (c : a) = update z−→∗ (LUNIT - FUN){z 7→ •, a 7→ z, b 7→ ch} : return (b : a) = update z−→ (LUNIT){z 7→ •, a 7→ z, b 7→ ch} : update z (b:a)−→ (UPDATE){z 7→ b : a, a 7→ z, b 7→ ch} : return z
The derivation terminates with a terminal program state at this point. Hence the initialprogram state is normal. The final heap contains the cyclic structure that represents theinfinite list of ch’s: The character that was read by getChar. In case elements of thislist are demanded in a context, the usual demand-driven rules modeled by our semantics
would let us produce enough elements to satisfy the need. If the input stream is empty tostart with, the derivation will simply block at the point where the GETC rule is applied,
and wait forever, i.e., the derivation will diverge by getting stuck.
Example 8.5.2 Showing that Example 8.2.2 diverges is fairly easy. We have:{} : fixIO (λc. putChar c = λd. return ’a’)−→∗ (FIXIO - FUN){z 7→ •, a 7→ z} : putChar a  λd. return ’a’
And now, we need to apply the PUTCEVAL rule to reduce the variable a to a character.
The functional layer first reduces a to z using the VAR rule, but gets stuck at that point,
as z is bound to • in the heap and the VAR rule does not apply anymore.
Example 8.5.3 We now reconsider Example 8.2.3, which involves reference cells. Again,
removing do-notation and simplifying the patterns, we get:fixIO (λt. newIORef (fst t) = λy.
return (1 : fst t, y))= λu. readIORef (snd u)
Since there are no calls to getChar, the input stream does not matter. That is, we will
simply copy the same input stream through all transitions in our derivation. Therefore,
we simply do not write it explicitly in what follows.

116

We will first consider the fixIO call. To save space, we will abbreviate newIORef to
new and readIORef to read:{} : fixIO (λt. new (fst t) = λy.return (1 : fst t, y))−→∗ (FIXIO - FUN){z 7→ •, a 7→ z} : new (fst a) = λy. return (1 : fst a, y) = update z−→ (NEWIO){z 7→ •, a 7→ z, b 7→ fst a} :νr.(return r = λy.return (1 : fst a, y) = update z | hbir )−→∗ (LUNIT - FUN){z 7→ •, a 7→ z, b 7→ fst a, c 7→ r} :νr.(return (1 : fst a, c) = update z | hbir )−→∗ (LUNIT - UPDATE){z 7→ (1 : fst a, c), a 7→ z, b 7→ fst a, c 7→ r} : νr.(return z | hbi r )When we consider the original expression, it is not hard to see that we will have:−→ (LUNIT - FUN){z 7→ (1 : fst a, c) , a 7→ z, b 7→ fst a, c 7→ r, d 7→ z} :νr.(read (snd d ) | hbir )−→ (READIOEVAL){z 7→ (e, f ), a 7→ z, b 7→ fst a, c 7→ r, d 7→ (e, f ), e 7→ 1 : fst a, f 7→ r} :νr.(read r | hbir )−→ (READIOREF){z 7→ (e, f ), a 7→ z, b 7→ fst a, c 7→ r, d 7→ (e, f ), e 7→ 1 : fst a, f 7→ r} :νr.(return b | hbir )Now, if we chase the value of b in the heap, we see that we will end up with a cyclic
structure effectively representing the infinite lists of 1’s, as intended. The most interesting
step in this derivation is the application of the READIOEVAL rule. The function snd isa short hand for case over the pairing constructor. The VAR rule in the functional layer
arranges for sharing, resulting in an abundance of variables in the resulting heap. Notice
that, abusing the notation slightly, in the above derivation (1 : fst a, c) refers to a function
application: the pairing constructor applied to the terms 1 : fst and c. In the last two

117

lines, however, (e, f) is a value, i.e., in this case, the pairing constructor applied to theright number of arguments.
Example 8.5.4 This example demonstrates the importance of the side condition of the
EXTRUDE rule. Consider:
do j ← new 5k ← new jl ← read kread l
By removing the do-notation, we get:new 5 = new = read = readWe will try to give a derivation for this expression, ignoring the side condition of the
EXTRUDE rule. Again the input stream is irrelevant, and hence ignored:{} : new 5 = new = read = read−→ (NEWIOREF){x 7→ 5} : νj.(return j = new = read = read | hxi j )−→∗ (LUNIT-NEWIOREF){x 7→ 5, y 7→ j} : νj.(νk.(return k = read = read | hyi k ) | hxij )−→ (COMM){x 7→ 5, y 7→ j} : νj.(hxij | νk.(return k = read = read | hyik ))−→ (EXTRUDE – incorrect application){x 7→ 5, y 7→ j} : νj.(hxij ) | νk.(return k = read = read | hyi k )−→∗ (LUNIT - READ - LUNIT){x 7→ 5, y 7→ j} : νj.(hxij ) | νk.(read y | hyik )−→ (READIOEVAL){x 7→ 5, y 7→ j} : νj.(hxij ) | νk.(read j | hyik )And now we are stuck! The mutable variable j is not visible at this point. Since wewere not careful in applying the extrude rule, we have created a dangling reference. Let
us construct the slice when the rule is applied:S0 = {y},S1 = {y, j},

S2 = S1 = S∞

By Equation 8.1, the slice is: {y 7→ j}. Since j ∈ fn({y 7→ j}), EXTRUDE is notapplicable. The side condition prevents the creation of the dangling reference.

118

8.6

Properties of fixIO

Equipped with the semantics we have presented so far, we are now in a position to lookat the properties of fixIO.
Strictness. Consider Equation 2.1, and let Γ be a heap where f is properly bound.
Assuming f is strict (Definition 8.4.21), we will have:I : Γ : fixIO f −→ I : (Γ, z 7→ •) : f z = update z
by a single application of the FIXIO rule. The current context specifies that the applicationf z should be evaluated. By Definition 8.4.21, the derivation will silently diverge. But
then, by Definition 8.4.19, this divergence implies that fixIO f is ⊥.
Example 8.6.1 Using if as a shorthand for case over the boolean type, consider:I : {} : fixIO (λx. if x == 0 then return 1 else return 2)−→ (FIXIO - FUN)
I : {z 7→ •, a 7→ z} : if a = 0 then return 1 else return 2 = update z... detected black hole ...
In the last step, the FUN rule is not applicable because there are no reductions for thecurrent term in the functional layer.
Example 8.6.2 Consider the following non-strict function:
λx. return x :: Char → IO Char
Notice that it returns a computation successfully. Of course, if the result of the fixedpoint computation is used, it will still diverge, but for a different reason:
I : {} : fixIO (λx. return x ) = putChar−→ (FIXIO - FUN)
I : {z 7→ •, a 7→ z} : return a = update z = putChar−→ (LUNIT - UPDATE - LUNIT)
I : {z 7→ a, a 7→ z} : putChar z... detected black hole ...
The last step diverges, because the VAR rule will get stuck trying to reduce z to a character.

119

Example 8.6.3 Consider the function:
λa. putChar ’q’  if a == 1 then return 1 else return 2which is not strict according to our semantics. Here is the derivation for it:
I : {} : fixIO (λa. putChar ’q’  if a == 1 then return 1 else return 2)−→∗ (FIXIO - FUN)
I : {z 7→ •, a 7→ z} :putChar ’q’  if a == 1 then return 1 else return 2 = update z!q−→ (PUTC)I : {z 7→ •, a 7→ z} : if a == 1 then return 1 else return 2 = update z... detected black hole ...
Before getting stuck, we see the character q printed, which is the correct behavior.
Purity. Consider Equation 2.2, where we will use a let expression to capture fix:
fixIO (return · h) = return (let a = h a in a)
Assume Γ is a heap such that ([ ] : Γ : h) :: τ → τ . On the left hand side, we have:I : Γ : fixIO (return · h)−→∗ (FIXIO - FUN)
I : (Γ, z 7→ •, a 7→ z) : (return · h) a = update z−→ (LUNIT)
I : (Γ, z 7→ •, a 7→ z) : update z (h a)−→ (UPDATE)
I : (Γ, z 7→ h a, a 7→ z) : return z
Considering the right-hand-side, we immediately get: I : Γ : return (let a = h a in a).
We should now prove that these two program states are equivalent, i.e., that the rulesin our system cannot tell them apart. Such an argument would require a notion of program
state equivalence that is more general than what our structural rules provide. Intuitively,
the program states above will be considered equivalent if we can show that
I : (Γ, z 7→ h a, a 7→ z) : z≡

I : Γ : let a = h a in a

Note that the second program state reduces to I : (Γ, z 7→ h z) : z. Hence, the equivalence is clear provided we adopt a compaction rule that gets rid of the indirection via a in

120

the first heap. To formalize this argument, we need a precise definition of program state
equivalence and a proof system for showing when two program states are the same. We
leave the development of a such a system for future work.
Left shrinking. Consider Equation 2.3, where we will refer to the computation a as q
to avoid confusion with heap variables. For the left hand side we get:I : Γ : fixIO (λx. q = λy. f x y)−→∗ (FIXIO - FUN)
I : (Γ, z 7→ •, a 7→ z) : q = λy. f a y = update z
On the right hand side, we have I : Γ : q = λy. fixIO (λx. f x y). Now, if thederivation for q diverges, both derivations will diverge in the exact same way, that is bothsides are equivalent. Otherwise, by Lemma 8.4.15, we will have:α

I : Γ : q −→ I 0 : ∆ : ν~r.(return qv | C)
The C on the right hand side captures the passive containers that might be introducedin the derivation for q, along with the associated restrictions ν~r. Since these containers
will get copied in exactly the same way, we do not show them explicitly in the following
discussion. Using the HEAPEXT and EXTRUDE rules silently, the left hand side yields:
I : (Γ, z 7→ •, a 7→ z) : q = λy. f a y = update z−→ (ASSUMPTION)α ∗I 0 : (∆, z 7→ •, a 7→ z) : return qv = λy. f a y = update z−→∗ (LUNIT, FUN)
I 0 : (∆, z 7→ •, a 7→ z, b 7→ qv) : f a b = update z
Let us look at the right hand side:I : Γ : q = λy. fixIO (λx. f x y)−→ (ASSUMPTION - LUNIT)α ∗I 0 : (∆, b 7→ qv) : fixIO (λx. f x b)−→∗ (FIXIO - FUN)
I 0 : (∆, b 7→ qv, z 7→ •, a 7→ z) : f a b = update z
Hence, the left shrinking property holds for fixIO. We conclude that, with respect to oursemantics, fixIO is a legitimate value recursion operator for the IO monad.

121

Other properties. As pointed out in Corollary 3.1.7, neither strong sliding nor rightshrinking properties hold for Haskell’s IO monad. Both of them fail with respect to thesemantics we have given in this chapter as well. (Application of our rules to functions usedin Propositions 3.1.5 and 3.1.6 suffices to show the failure in both cases.) We believe thatsliding and nesting properties should hold, both for Haskell’s IO monad and our semantics.
We leave the construction of proofs for these properties for future work.

8.7

Summary

In this chapter, we have described an operational semantics for a non-strict functional
language extended with monadic IO, references, and value recursion, improving on ourearlier work [20]. Our contributions are: (i) we show how a purely functional language
and its semantics can be embedded into a language with monadic I/O primitives andreferences, (ii) we model sharing explicitly at all levels, giving an account of call by needin both the functional and the IO layers, and (iii) we provide a semantics for fixIO andshow that it is a value recursion operator.
Our work can be extended in several ways. Addition of threads and synchronizedvariables seems to be fairly easy [67]. The difficulty, however, lies in adding support for
asynchronous exceptions [56]. Although exceptions can be modeled nicely in the IO layer,
we currently do not see a complementary way of capturing them in the functional layerusing our method.
More work is needed in formalizing our arguments. Of the highest importance is thedevelopment of a notion of program equivalence, and tools for reasoning about program
states which may contain symbolic terms. In this direction, program equivalence basedon observational behavior seems to be the right framework [27, 61].
One important issue we have side-stepped in this chapter is that of parametricity. How
do we know that the constants of our language (i.e., return, =, fixIO, newIORef, etc.)
are parametric? To talk about parametricity, we first need to define what it means fortwo program states to be related. Our earlier attempts at stating and establishing parametricity failed, mainly due to the lack of an appropriate notion of program equivalence.
Pitts’s work on observational equivalence and parametric polymorphism [71] can be used
as a basis for such a work, although it is not immediately clear how to accommodate forreferences and input/output operations. Similarly, Launchbury and Peyton Jones discussparametricity of constants for manipulating references in the context of the state monadof Haskell [52], but their results are not directly applicable in our framework due to differences in the notion of reference variables, handling of the heap, and the additional
complexity introduced by input/output.

Chapter 9
ExamplesIn this chapter, we will consider a number of practical programming examples, illustrating
the use of value recursion operators and the mdo-notation. 1
Synopsis. Starting with the famous repmin problem, we consider applications in sorting
networks, screen layout in GUI’s, interpreters, cyclic graphs, and the implementation oflogical variables.

9.1

The repmin problem

The repmin problem is concerned with the replacement of all the numbers in a binary tree
by their minimum. The challenge is to do so in a single pass [6, 16]. In 1984, Richard Bird
devised a beautiful solution to this problem, exploiting laziness and cyclic definitions:data Tree α = L α | B (Tree α) (Tree α) deriving Showcopy:: Tree Int → Int → (Tree Int, Int)

copy (L a) m = (L m, a)
copy (B l r ) m = let (l 0 , ml ) = copy l m(r 0 , mr ) = copy r min (B l 0 r 0 , ml ‘min‘ mr )repmin :: Tree Int → Tree Int
repmin t = let (t 0 , m) = copy t m in t 0
Here’s an example run:
Main> repmin (B (L 11) (B (L 2) (L 3)))B (L 2) (B (L 2) (L 2))
1

Before proceeding with the examples in this chapter, the reader may want to review our motivating
circuit modeling example, covered in Sections 1.2 and 7.1.

122

123

The single pass solution is achieved by the clever use of recursion in the let-expression of
the function repmin. By virtue of the recursive binding, the function copy simultaneously
computes and replaces all the leaves with m, the minimum value in the tree.
Benton and Hyland take the problem one step further [5]. What if we also wantto perform an effect, such as printing the values stored in the nodes during this single
traversal as well? It is easy to modify copy to achieve this effect:
copyPrint
copyPrint (L a):: Tree Int → Int → IO (Tree Int, Int)m = do print a
return (L m, a)

copyPrint (B l r ) m = do (l 0 , ml ) ← copyPrint l m(r 0 , mr ) ← copyPrint r mreturn (B l 0 r 0 , ml ‘min‘ mr )But, it is not clear at all how to modify repmin accordingly. Obviously, the attempt:
copyPrint t m = λ(t 0 , m). return t 0
is flawed, since m is no longer recursively bound! We need to tie the recursive knot with
an appropriate value recursion operator. In this particular case, the appropriate operator
is the one for the IO monad, i.e., fixIO of Chapter 8:
repminPrint:: Tree Int → IO (Tree Int)

repminPrint t = fixIO (λ˜ (t 0 , m). copyPrint t m)
= λ(t 0 , m). return t 0
Or, using the mdo-notation:
repminPrint :: Tree Int → IO (Tree Int)
repminPrint t = mdo (t 0 , m) ← copyPrint t mreturn t 0
hiding the explicit call to fixIO, considerably improving readability.
Note that we can accommodate arbitrary effects during the traversal of the originaltree, as long as the underlying monad comes equipped with a value recursion operator.
To illustrate, consider the following variation of the repmin problem, demonstrating theuse of value recursion for the list monad (Section 4.3). Consider the data type:
data Exp = C Int | A Exp Exp
representing simple arithmetic expressions formed out of integer constants and additions.
The problem is to find all possible pair-swaps of a given expression. A swapping is defined

124

to be the exchange of any two constants, not necessarily distinct. 2 Solving the swappings problem is not a terribly hard task. Here, we present a particularly neat solution,
illustrating the use of value recursion for the list monad:
replacereplace x (C y):: Int → Exp → [(Exp,Int)]= [(C x , y)]replace x (A l r ) = [(A l 0 r , y) | (l 0 , y) ← replace x l ]++ [(A l r 0 , y) | (r 0 , y) ← replace x r ]pairSwaps:: Exp → [Exp]pairSwaps e = mdo (e 0 , m) ← replace n e(e 00 , n) ← replace m e 0
return e 00
The call replace x e creates copies of e, where each copy has one of its constants
replaced by x. Each replaced constant is returned along with the corresponding copy. (Ifthere are n constants in e, the call to replace will return n copies.) For instance:
replace 1 2=⇒ [(1, 2)]

replace 1 (2 + 3) =⇒ [(1 + 3, 2), (2 + 1, 3)]
The function pairSwaps makes two successive calls to replace, threading the input
expression through. The first call replaces each constant with n (yet to be computed),
determining the respective values for m. The second call completes the swapping bysubstituting m’s, and by computing the values of n needed in the first call. Each pairingof m and n corresponds to a possible swapping. The cyclic dependence between m and nachieves the required swapping quite neatly.
Here is an example run for the input (1+2)+3, using appropriate functions for parsing
and printing:Main> display (pairSwaps (parse "(1 + 2) + 3"))[(1 + 2) + 3, (2 + 1) + 3, (3 + 2) + 1,
(2 + 1) + 3, (1 + 2) + 3, (1 + 3) + 2,
(3 + 2) + 1, (1 + 3) + 2, (1 + 2) + 3]
The value recursion operator used implicitly in the definition of pairSwaps is the one
given by Equation 4.4. Recall that we have considered an infinite family candidate operators for the list monad in Section 4.3 (see Equation 4.13). We have argued that these2

For instance, the only possible swapping of 1 is 1, while that of 1 + 2 are 1 + 2, 2 + 1, 2 + 1, and 1 + 2.
The two 2 + 1’s are considered different, corresponding to the swappings of 1–2 and 2–1. It is easy to seethat an expression with n constants will have n2 swappings, one for each pair of constants.

125

candidates behave strangely, violating the mandatory left shrinking property. We takethis opportunity to show that they yield weird results for the swapping problem as well.
For instance, the use of mfix1 yields:
Main> display (pairSwaps1 (parse "(1 + 2) + 3"))[(1 + 2) + 3, (2 + 1) + 3, (2 + 2) + 1,
(2 + 2) + 3, (1 + 2) + 3, (1 + 2) + 2,
(3 + 2) + 2, (1 + 3) + 2, (1 + 2) + 3]
producing illegal swappings such as (2 + 2) + 1. The failure of the left-shrinking property
causes unwanted interference when the constants are paired. 3

9.2

Sorting networks and screen layout in GUI’s

A sorting network is a collection of comparators, connected in such a way that the outputof the network is always the sorted permutation of its input [15]. For instance, the following
network can sort four numbers:
a
b

e

1
f

3

c
d

i
2

g

h

4

k

j

5

l

m

n

For each comparator, the wire to its right carries the maximum of its inputs, while thelower one carries the minimum. In this particular example, a, b, c, and d are the inputs,
while k, l, m, and n are the outputs.
How can we implement a sorting network so that we not only get the values sorted, butalso a transcript of the operations performed during sorting? We want each comparator
unit to report on the operation it performed while sorting took place. The output monad3
In certain cases, the operation of the value recursion operator for the list monad can be understood interms of the usual translation rules for list-comprehensions [89], using symbolic substitution for variables
that occur recursively [18, Sections 1 and 6.3]. The details, although not terribly important, might beenjoyable for the curious reader, providing some more insight about the behavior of mfix for the list monad.

126(Section 4.5) springs to mind. We can translate the sorting network above almost literally
into the following Haskell code:
newtype Out α = Out (α, String)
instance Monad Out wherereturn x= Out (x , “ ”)Out ˜ (x , s) = f = let Out (y, s 0 ) = f x in Out (y, s ++ s 0 )instance Show α ⇒ Show (Out α) whereshow (Out (v , s)) = show v ++ scomp:: Int → (Int, Int) → Out (Int, Int)
comp i (a, b) = Out ((a ‘max ‘ b, a ‘min‘ b), “\nUnit ” ++ show i ++ msg)
where msg = (if a < b then “: swap: ” else “: pass: ”) ++ show (a, b)
sort4:: (Int, Int, Int, Int) → Out (Int, Int, Int, Int)
sort4 (a, b, c, d ) = do (e, f ) ← comp 1 (a, b) -- unit 1(g, h) ← comp 2 (c, d ) -- unit 2(n, i) ← comp 3 (e, g) -- unit 3(j , k) ← comp 4 (f , h) -- unit 4(m, l ) ← comp 5 (i , j ) -- unit 5
return (k , l , m, n)
Here is a sample run:
Main> sort4 (23, 12, -1, 2)(-1,2,12,23)
Unit 1: pass: (23,12)
Unit 2: swap: (-1,2)
Unit 3: pass: (23,2)
Unit 4: pass: (12,-1)
Unit 5: swap: (2,12)What happens if we want to observe the output in some different order? For instance,
we might want to see the output of the third unit after the fifth. Intuitively, it must be
sufficient to move the third line after the fifth in the definition of sort4, obtaining:
sort4 (a, b, c, d) = do (e, f ) ← comp 1 (a, b) -- unit 1(g, h) ← comp 2 (c, d) -- unit 2(j , k ) ← comp 4 (f , h) -- unit 4(m, l ) ← comp 5 (i, j ) -- unit 5(n, i) ← comp 3 (e, g) -- unit 3
return (k, l, m, n)127

Alas, this modification is illegal: The variable i is unbound when used in the fourth line.
Luckily, value recursion fits the bill. All we need to say is that the variable i used by the
5th unit is the one that is defined by the 3rd, which can be handled by an mdo-expression.
As we have seen in Section 4.5, the corresponding mfix is given by:
instance MonadFix Out where
mfix f = let Out (a, s) = f a in Out (a, s)With this declaration and the use of the keyword mdo, sort4 will work as expected,delivering the output of the third unit after that of the fifth.
A similar phenomenon occurs in GUI based programming, where the order of monadic
actions implicitly determines the screen layout. To illustrate, consider the following simple
example, taken from Thiemann’s work on a CGI library for Haskell [85]:do f1 ← inputField (fieldSize 10)
f2 ← inputField (fieldSize 10)
submitButton (someAction f1 f2 )The corresponding GUI will have two input fields side by side, followed by a submit
button. What happens if we want to place the submit button to the left of the input
fields? Since the ordering of the statements in the do-expression determines the positionof the GUI elements, we would like to move the call to submitButton to the first line,
textually preceding the calls to inputField. As Thiemann also points out, such a move
would require the use of an mdo-expression, since the variables f1 and f2 will no longer bevisible when used as arguments to someAction.

9.3

Interpreters

Suppose you are designing an interpreter for a language that has let-bindings for introducing local bindings. Operationally, the expression let v = e in b denotes the same
expression as b, where e is substituted for all free occurrences of the variable v. The
abstract syntax of your language might include:
data Exp = ... | Let Var Exp Exp
Assuming the language is applicative, the natural choice for implementation would be
the environment monad (Section 4.6). In this setting, the section of the interpreter that
handles the let-expressions might look like:eval (Let v e b) = do ev ← eval e
inExtendedEnv (v , ev ) (eval b)

128

where inExtendedEnv simply extends the environment with the binding v 7→ ev before
passing it on. This approach yields a satisfactory implementation.
Note that, our eval function cannot deal with recursive bindings, i.e., in the expression
Let v e b, v is not visible in e. What happens if we lift this restriction? All we needis a way to extend the environment with the binding v 7→ ev in the call to eval e,
before we actually know what ev is. The following mdo-expression expresses the required
dependency:
eval (Let v e b) = mdo ev ← inExtendedEnv (v , ev ) (eval e)
inExtendedEnv (v, ev) (eval b)
In contrast, consider how we might solve this problem without using value recursion.
Assuming Val denotes the data type for the values our language can process, and thefollowing declaration of environments:
data Env α = Env ([(Var , Val )] → α)
we are forced to implement recursive let-expressions as follows:
eval (Let v e b) = Env (λenv. let Env f = eval eev= f ((v , ev ) :env)
Env g = eval b
in g ((v , ev ) :env ))Although it will perform the required task, this solution is hardly satisfactory. Firstof all, we had to reveal how environments are actually implemented, defeating the whole
point of the monadic abstraction. As a result, our code will only work with that particular implementation; switching to a different representation will require changes in theinterpreter. The code is no longer easy to understand or maintain.
On the other hand, our first implementation using the mdo-notation is quite simple tounderstand, concise, and not tied to any particular representation of environments.

9.4

Doubly linked circular lists with mutable nodes

Consider a simple implementation of doubly linked circular lists in Haskell. For thisexample, we will store a mutable boolean flag at each node, a True value indicating thatthe node is already visited in a particular traversal. We use the internal state monad togain access to mutable variables [52]. The nodes in our circular lists have the following
structure:
newtype Node s α = N (STRef s Bool , Node s α, α, Node s α)

129consisting of the mutable flag, the pointer to the previous node, the data item, and thepointer to the next node. Given two nodes b and f, a new node in between is created by
the following function:
newNode:: Node s α → α → Node s α → ST s (Node s α)
newNode b c f = do v ← newSTRef False
return (N (v , b, c, f ))Here is a simple example of a circular list, and its rendering in Haskell using thefunction newNode. Note that the use of the mdo-expression is essential in expressing thecyclic structure:4
ll :: ST s (Node s Int)ll = mdo n0 ← newNode n3 0 n1

ll

0

1

2

n1 ← newNode n0 1 n2n2 ← newNode n1 2 n33n3 ← newNode n2 3 n0
return n0
Traversing a given doubly linked list simply amounts to following the links until wereach a node that has been visited before:
data Direction = Forward | Backward deriving Eq:: Direction → Node s α → ST s [α]traverse

traverse dir (N (v , b, i, f )) =do visited ← readSTRef v
if visitedthen return [ ]else do writeSTRef v True
let n = if dir == Forward then f else bis ← traverse dir nreturn (i:is)
Here’s a sample run:
4

A more traditional technique would rely on creating dummy initial link values for at least one of thenodes, and explicitly overwriting them when the rest of the structure is created. This “clunky” approach
is often seen in the formation of cyclic objects in imperative languages, such as Java. Perhaps an mdo-likeconstruct could help there also.

130

Main> runST (ll >>= traverse Forward)[0,1,2,3]
Main> runST (ll >>= traverse Backward)[0,3,2,1]
The inverse function that takes a non-empty list and constructs a doubly linked circular
list out of its elements further illustrates the use of value recursion:
encircle:: [α] → ST s (Node s α)

encircle (x :xs) = mdo c ← newNode l x f(f , l) ← encircle 0 c xs
return cencircle 0
encircle 0 p [ ]:: Node s α → [α] → ST s (Node s α, Node s α)
= return (p, p)

encircle 0 p (x :xs) = mdo c ← newNode p x f(f , l ) ← encircle 0 c xs
return (c, l )We have:
Main> runST (encircle "hello world" >>= traverse Backward)"hdlrow olle"
Main> runST (encircle "hello world" >>= traverse Forward)"hello world"
Similar techniques might be useful in the functional implementation of graph algorithms as well [45]. In general, programs manipulating stateful objects with cyclic dependencies can benefit from value recursion. For instance, Nordlander shows how to use
value recursion to express layered networking protocols in the context of his O’Haskell
language [65, Section 4.2].

9.5

Logical variables

In a tutorial paper on monads and effects, Benton, Hughes and Moggi suggest the following
exercise on programming with monads [4, Exercise 55]:
Prolog provides so-called logical variables, whose values can be referred tobefore they are set. Define a type LVar and a monad Logic in terms of ST,
supporting operations:

131

newLVar :: Logic s (LVar s α)
readLVar :: LVar s α → α
writeLVar :: LVar s α → α → Logic s ()where s is again a state-thread identifier. The intention is that an LVar should
be written exactly once, but its value maybe read beforehand, between its
creation and the write—lazy evaluation is at work here. Note that readLVar
does not have a monadic type, and so can be used anywhere.
Clearly, we will need to use value recursion in implementing newLVar, allowing us toaccess the value of a logical variable before it is actually set. There is a small problem,
however. How do we determine the scope of a logical variable, i.e., how do we make itavailable to the rest of the computation? We solve this problem by using the continuationmonad transformer, a clever trick suggested to us by John Hughes. 5 Using this idea, theLogic monad looks like:data Logic s α = Logic {unL :: forall τ . (α → ST s τ ) → ST s τ }instance Monad (Logic s) wherereturn a= Logic (λk. k a)
Logic f = g = Logic (λk. f (λa. unL (g a) k ))A logical variable is nothing but a value and a pointer to it. To read, we simply project
the value. To write, we update the mutable cell:newtype LVar s α = LVar (STRef s α, α)readLVar:: LVar s α → αreadLVar (LVar ( , v)) = v
writeLVar:: LVar s α → α → Logic s ()writeLVar (LVar (r , )) a = Logic (λk. do writeSTRef r ak ())
The magic that makes logical variables work is hidden in newLVar:
newLVar :: Logic s (LVar s α)
newLVar = Logic (λk. mdo r ← newSTRef (error “unbound LVar!”)a ← k (LVar (r , v))
v ← readSTRef r
return a)5

An alternative would be to use the type newLVar :: (LVar s a → Logic s b) → Logic s b, requiring
the user to explicitly specify the scope, as in: newLVar (λv. . . .). In that case, the ST monad itself would
serve as the Logic monad, without any need for continuations.

132

Here is how newLVar works. We allocate a new mutable variable, r, and form the pair(r, v), where v is the value that will eventually be stored in r. This pair is passed tothe continuation k, representing the remainder of the computation, i.e., the scope of the
new logical variable. Before returning the result of this call, we simply read the mutablevariable r, determining the actual value of v. (Note that the computation represented by kis expected to call writeLVar on the newly created logical variable, setting its final value.)
The mdo-expression implicitly uses the function fixST, the value recursion operator for
Haskell’s internal state monad (see Section 4.4). The final bit of machinery we need is a
simple run method to extract values:
runLogic:: (forall s. Logic s τ ) → τ
runLogic f = runST (unL f return)
Here are a some simple examples demonstrating the use of LVar’s:
t1 = do v ← newLVar
let val = readLVar v
return val

t2 = do v ← newLVar
let val = [0, 6 .. readLVar v ]writeLVar v 42
return val

t3 = do v ← newLVar
let v1 = readLVar v
writeLVar v 43
let v2 = readLVar v
writeLVar v 42
let v3 = readLVar v
return (v1 , v2 , v3 )t4 = do s ← newLVarc ← newLVar
let sVal = readLVar s
cVal = readLVar c
writeLVar s “test”
writeLVar c ’l’return (cVal : sVal )We have:
Main> runLogic t1 :: Int
Program error: unbound LVar!
Main> runLogic t3(42,42,42)

Main> runLogic t2[0,6,12,18,24,30,36,42]
Main> runLogic t4"ltest"In t1, we never write to v, hence its value is left undefined. All calls to writeLVar
except the last will be ignored,6 as demonstrated by t3. Finally, t4 shows that we can use
variables with different types in the same computation.
Claessen and Ljunglöf show how one can use logical variables to embed a typed functional logic programming language in Haskell [13]. Similar to our implementation, they6

Of course, every call to writeLVar will be performed when the computation is run, in the given order.
However, all calls to readLVar will return the last value written, regardless of their order. For all practical
purposes, logical variables behave as constants, whose values can be used before they are set.

133

use the ST monad to get access to typed mutable variables. However, they only allow
access to logical variables inside their version of the Logic monad. (In our terms, their readLVar function has the type LVar s α → Logic s α.) It might be interesting to combine
their work with ours, allowing logical variables to be used anywhere, and hence providing
a more flexible embedding. We leave the exploration of this idea for future work.

9.6

Summary

In this chapter, we illustrated the use of value recursion operators and of the recursive
do-notation. We admit that some of our examples might seem a little contrived, with
the notable exception of the circuit modeling example of Section 1.2. However, it is ourhope that readers will be able to relate these examples to their own work, spotting further
applications for value recursion. Here are some common cases to watch for:• Programs dealing with data flow equations. Assuming the underlying model ismonadic, any feedback loop or a cyclic dependency would signal the need for avalue recursion operator to tie the recursive knot. Our circuit modeling example isan instance of this problem.
• Stateful objects with mutual dependencies. Again, if a monadic interface is used,mutual dependencies will require the use of value recursion. Our implementationof doubly linked circular lists, and the network programming example in O’Haskell(see Section 10.1 for a brief discussion) are examples of this kind.
• Programs that combine several phases and use recursion to eliminate multiple traversals of data structures, similar to the repmin problem of Section 9.1. If any one of
the eliminated phases require monadic effects, value recursion becomes the tool for
expressing the required cyclic dependence.
• Monadic programs where a particular ordering of effects forces us to use variables
that will only become available later, similar to the sorting networks or GUI design
examples of Section 9.2.

Chapter 10
EpilogueIn this thesis, we have studied the interaction between two fundamental notions in programming languages: Recursion and effects. As we have seen, cyclic definitions in thepresence of monadic effects can be understood in terms of value recursion operators, whose
behavior can be characterized by means of a number of equational properties. It is ourbelief that these properties capture the essence of the interaction satisfactorily. Of course,
the extent to which our axiomatization is successful will only be determined by practice.
Our properties could be deemed appropriate if they rule out useless definitions of value
recursion operators, and admit only those that are meaningful in practical programs. It is
still too early to come to a decisive conclusion in this regard, but we hope that our work
will be useful for both researchers and practitioners, especially as monads become more
and more pervasive in functional programming.
We conclude our exposition of value recursion by briefly reviewing the related work,and pointing out some future research opportunities.

10.1

Related work

The interaction between recursion and shared computations has been extensively studied
by Hasegawa [32, 33]. Sharing is a commutative effect, i.e., the order of computations does
not matter.1 As we have explored in the first part of Chapter 6, recursion in commutative
monads can be understood in terms of traces in symmetric monoidal categories. Hasegawa
shows that giving a trace over a cartesian closed category is the same as giving a fixed-point
operator for it (see Theorem 6.2.4). This result is remarkable, as it provides an escapefrom the usual domain-theoretic view, increasing the level of abstraction considerably. As
Hasegawa himself points out, however, when the underlying effect is non-commutative, we
can no longer stay in the monoidal world.
1

Think of a recursive let-expression in Haskell. The order of bindings is irrelevant; equations can beswapped around without changing the result.

134

135

Paterson introduced loop operators for handling value recursion in arrows [36, 66].
Although Paterson notes that some of the axioms are too strong for many practical cases,
he does not weaken his axiomatization to accommodate accordingly. On the syntactic
side, Paterson’s work introduced a convenient notation for programming with arrows in
Haskell, providing support for recursive bindings as well. However, rather than letting thetranslation figure out recursive segments as we do in the mdo-notation, Paterson prefers
using an explicit keyword, rec, asking the programmers to mark recursive blocks explicitly.
(The rec keyword is modeled after O’Haskell’s handling of recursive bindings, reviewed
below.)
Building on our initial paper of monadic fixed-points [18], Benton and Hyland take
Hasegawa’s work one step further by generalizing the notion of trace to premonoidal
categories [5]. (It turns out that Benton and Hyland’s axiomatization and Paterson’s
work on arrows are essentially the same, although developed independently and presented
in slightly different contexts.) Similar to Paterson’s loop axioms, Benton and Hyland’s
axiomatization is too strong for many monads as well. As we have seen in the second halfof Chapter 6, their sliding and right tightening laws are simply not satisfiable in many
practical cases (see also Section 3.1). As a consequence, their work can explain value
recursion for the state monad (and those monads that embed into it, such as output andenvironments), but not exceptions, lists, or the I/O monad. In general, any monad thatis based on a sum-like data type will fail to satisfy their requirements. In any case, we
consider Paterson and Benton and Hyland’s work as an important step toward a categoricalaccount of value recursion.
Friedman and Sabry [25] approach value recursion form an entirely different angle.
Rather than considering individual monads separately, they consider recursion itself as acomputational effect, following an operational definition: Allocate a reference cell, evaluate
the body, and update the cell with the result. (This process is essentially how Scheme
models recursion, as we have briefly covered in Section 5.3.) Since recursion is performedin the combined monad, it is the users’ responsibility to translate original problems andvalues to and from this combined world. That is, to model value recursion in a monad m,they end up using a function:
mfixM :: (STM s m α → STM s m α) → STM s m α
where STM is the state monad transformer. 2 Furthermore, all the morphisms of thebase monad have to be lifted into this “state enriched” world as well, and this is where2Note that mfixM accepts a function from computations to computations, rather than from values tocomputations as in the case for mfix. This change of view is necessary for implementing the allocateevaluate-overwrite model.

136

the interaction between particular effects and recursion has to be addressed by the user.
Unlike us, however, they do not postulate any properties, hence it is up to the user tocome up with correct liftings. As Friedman and Sabry observe themselves, their method
is rather inconvenient to use from a programming perspective, compared to our mdoexpressions and direct handling of recursion in the given monad. Unfortunately, a similar
comparison is not immediately possible from a theoretical point of view, as the approaches
are fundamentally different.
From a practical point of view, much greater similarity to our work is found in Nordlander’s O’Haskell language. O’Haskell is an object oriented extension of Haskell, designed
for addressing issues in reactive functional programming [65]. One application of O’Haskell
is in programming layered network protocols. Each layer interacts with its predecessor
and successor by receiving and passing information in both directions. In order to connect
two protocols that have mutual dependencies, one needs a recursive knot-tying operation. Since O’Haskell objects are monadic, value recursion is employed in establishingsuch connections. O’Haskell adds a keyword fix to the do-notation, whose translation isa simplified version of our mdo-notation. The O’Haskell work, however, does not try toaxiomatize or generalize the idea any further.
Carlsson and Hallgren discuss a variety of loop operators in the context of their workon stream based programming using fudgets [29]. Although the intended semantics oftheir loop operators is quite similar to those of value recursion operators, the types andthe mechanics are somewhat different. For instance, one of their operators has the type:loopLeftF :: F (Either α β) (Either α γ) → F β γ
which, intuitively, ties the recursive loop over α, resulting in a fudget from β to γ. Carlssonand Hallgren use loop operators only in the framework of fudgets, without generalizing toarbitrary monads, or studying their behavior more abstractly.
The circuit modeling example we have seen in Section 1.2 is discussed in detail in
Claessen’s recent dissertation [12]. Although Claessen points out the need for an appropriate looping combinator, he does not pursue the monadic approach any further. Instead,he introduces the notion of observable sharing, which is a non-conservative 3 extension to
Haskell [14]. (Briefly, observable sharing allows programmers to determine whether a circuit component is reached via a feedback loop, solving the infinite unfolding problem.)Claessen argues that “...loop combinators are unfortunate because they introduce extra
clutter in the code that is hard to motivate” [12]. We believe that our mdo-notation
addresses Claessen’s concerns perfectly, relieving the programmers from error-prone and3

Since the addition of observational sharing violates referential transparency, the resulting language is
no longer pure. That is, the law: let x = M in N ≡ N [fix (λx. M )/x ] no longer holds.

137

cumbersome uses of explicit looping combinators. In addition, the monadic approach has
the obvious advantage of keeping the underlying language pure, providing a nice and clean
semantic framework.
Turbak and Wells introduce the cycamore data type, which is aimed at simplifying
the use of cyclic structures in declarative languages [86]. The basic idea is to associateeach node in a cycamore with a global unique identifier, similar to our doubly linked list
example of Section 9.4. They consider implementations in both ML and Haskell, and the
Haskell version makes use of references in the state monad to implement unique identifiers.
As expected, Turbak and Wells employ value recursion in order to express the required
cyclic structure.

10.2

Future Work

Although we have concentrated on applications in functional programming, value recursion
certainly makes sense in other programming paradigms as well. One future research direction to explore is the problem of creating cyclic structures in imperative languages. Suchstructures arise quite frequently in practice. For instance, the following example presents
an opportunity in IBM’s data manipulation language for its DB2 database system [11]:4
create type VDept_t as(name Varchar(20)) mode db2sql;
VPerson_t
VDept_tcreate type VPerson_t as(name Varchar(40)) mode db2sql;

mgr
dept

VEmp_tcreate type VEmp_t under VPerson_t as(dept Ref(VDept_t)) mode db2sql;
alter type VDept_t
add attribute mgr Ref(VEmp_t);

In this example, the user creates three types: department, person, and employee. Eachdepartment has a name and a manager. Each person is identified by a name. Finally, each
employee is a VPerson t, which further has a (reference to a) particular department. Whilethe create type directives for VPerson t and VEmp t reflect the structure correctly, theVDept t type cannot be created with both of its required attributes. Clearly, the difficultyarises as the VEmp t type is not yet visible when VDept t is created. The final alter typedirective remedies the situation in a roundabout fashion, adding the missing attribute.
4

This example was pointed out to us by David Maier.

138

We see two opportunities with regard to our research. First of all, better syntactic
support (along the lines of our mdo-notation) would help get rid of the final alter typedirective, keeping the declaration of VDept t self-contained, possibly simplifying further
analyses. More importantly, if such declarations are ever given a monadic semantics, value
recursion would be the right tool for modeling the cyclic dependency. Similar opportunities
exist in other languages as well.
On the theoretical side, we would like to see value recursion studied in a more abstract
setting. In this regard, the trace-fixed point correspondence, as we have studied in Chapter 6, seems to be the right direction to proceed. We would like to investigate the reasons
why the axiomatization via traces turns out to be too strong, hopefully augmenting thetheory to capture the practical aspects more precisely. For instance, it would be interesting to pin down the role of the right shrinking property precisely. As we have seen in
Chapter 3, right shrinking property is not satisfiable whenever the = operator is strictin its first argument, and hence a weakening of the trace-based axiomatizations seems
inevitable.
Several questions remain to be explored regarding the behavior of value recursion operators. For instance, we lack a reasoning principle along the lines of fixed-point induction.
Recall that the fixed-point induction principle states that P (fix f) can be established byshowing that P ⊥ ∧ ∀d.(P d ⇒ P (f d)) holds, provided P is an admissible predicate.
(The obvious generalization: P ⊥ ∧ ∀d.(P d ⇒ P (d = f)) ⇒ P (mfix f) is not sound,as it implicitly assumes an unfolding view of value recursion.) It is probably the case that
one needs to formulate and prove a separate induction principle for each new mfix, rather
than looking for a universal principle that would work for all cases. While our properties
provide a framework for reasoning about terms involving mfix, such an induction principle
might prove essential for reasoning about value recursion in general.
Another question is the automatic construction of value recursion operators for arbitrary monads. Although we have seen many “design patterns,” it is still not clear
how to define an appropriate operator for a given monad that will satisfy our properties.
(The continuation monad seems to be the problem child in this regard.) Although it ishighly unlikely that a magic recipe for automatic construction of such operators exists, itwould be nice to pin down the exact conditions under which their existence (and possibly
uniqueness) can be guaranteed.
The semantics we have presented in Chapter 8 for modeling monadic I/O needs some
improvements to simplify reasoning with symbolic terms. Furthermore, we would liketo extend our language to support more features, such as concurrency and exceptions.
While concurrency seems relatively easy to support, it is not immediately clear how toextend our system to include Haskell’98 style exceptions. More importantly, it would be139interesting to show that the addition of monadic I/O primitives, mutable variables, andsupport for value recursion preserves the parametricity principle. Also, we would like todesign an accompanying abstract machine semantics, which might be useful as a basis forconstructing interpreters for similar languages.
Whether the mdo-notation should eventually replace the current do-notation in Haskell
is a question that will have to be answered by the Haskell community. While we believethat a single construct should handle both recursive and non-recursive cases, such a change
potentially breaks existing programs, and it might be a better idea to make the switch ina future version of Haskell.

Appendix A
Fixed-point operators
In this appendix, we briefly review fixed-point operators. Our aim is to introduce theterminology we use, providing pointers to the literature for details as necessary.
In the domain theoretic semantics of programming languages, types are modeled bydomains and functions are modeled by continuous maps. The meaning of a typical recursive declaration of the form let x = M in N is taken to be N [fix (λx.M )/x ], where
fix f =

Gf i ⊥α(A.1)

iassuming M has type α. Note that x need not be a function only, we might define recursive
values this way as well. For instance, we have (using Haskell-like notation):≡let ones = 1 : ones in onesfix (λones. 1 : ones)

The least fixed-point theorem states that fix f is the least fixed-point of f [76, 92].
That is, (i) it satisfies the fixed-point property: f (fix f) = fix f, and (ii) it is the least
such value, i.e., for all x s.t. x = f x, we have fix f v x. We use the name fix only tomean this particular fixed-point operator over domains.
The theory of fixed points is extensively studied [9, 10, 81]. It is neither possible, nornecessary for us to summarize this huge body of work here; we will simply state the resultsthat are most relevant to our work.
Property A.1(Dinaturality.) Let f :: α → β, g :: β → α. The dinaturality 1 property

of fix states that:fix (f · g) = f (fix (g · f ))1

The term dinaturality refers to the fact that fix can be viewed as a dinatural transformation between
certain functors [55, 80]. We will not need this level of detail in our work, so we skip the details.

140

141

Property A.2 (Bekič.) Let f :: α × α → α. The Bekič property of fix states that:fix (λx. fix (λy. f (x , y))) = fix (λx. f (x , x ))Or, equivalently,
fix (λt. fix (λv. f (π1 t, π2 v ))) = fix fwhere f :: α × β → α × β. It is easy to generalize to arbitrary number of variables, rather
than just two; see Winskel’s textbook for details [92].2
In Chapter 6, we consider fixed-point operators in more abstract settings, i.e., without
assuming that the underlying structure is domains and continuous maps. We assume aminimal acquaintance with category theory in the following discussion [2, 70]. The basic
structure we work with is a category C with finite products. We write for the terminal

object. The set of arrows between two objects A and B is denoted C(A, B). We will need
the following basic definitions [33, 79]:
Definition A.3 A fixed-point operator is a family of functions (·) ∗A : C(A, A) → C( , A),such that for any f : A → A, f · f ∗ = f ∗ .
Definition A.4 A parameterized fixed-point operator is a family of functions:(·)†A,X : C(A × X, X) → C(A, X)
satisfying:• Parameterized fixed-point property: For f : A × X → X, f · hid A , f † i = f † .
• Naturality in A: For f : A × X → X and g : B → A, (f · (g × id X ))† = f † · g.
Definition A.5 A Conway operator is a parameterized fixed-point operator that further
satisfies:• Dinaturality: For f : A × X → Y and g : A × Y → X, (g · hπ 1A,X , fi)† = g · hidA , (f ·hπ1A,Y , gi)† i.
• Diagonal property: For f : A × X × X → X, f †† = (f · (idA × hidX , idX i))† .
The reader need not master these definitions in full, only a basic familiarity is sufficient.
For the most part we will be working with fix, and using the dinaturality and Bekič
properties given before, which are much easier to read and understand.
2

Bekič’s property appears in many different but equivalent forms in the literature [3]. The versions wehave given here are the ones that are most suitable for our purposes.

Appendix B
ProofsIn the following proofs, we assume true products. In the case of lifted products, special caremust be taken to ensure that the difference between (⊥, ⊥) and ⊥ is not visible. The cases
when the distinction does matter have been pointed out in the text. (See Warning 2.6.7
as well.)
To save space, we will shorten return to η in our proofs. Also note that we use the namemap to refer to Haskell’s fmap, i.e., map :: (α → β) → m α → m β for all monads m,
defined by the equation map f m = m = η · f.

B.1

Proposition 2.5.2

Given Equation 2.7, establishing 2.8 is easy. We have:
mfix (λ(x , ). mfix (λ( , y). f (x , y)))= mfix (λt. mfix (λu. f (π1 t, π2 u)))= mfix (λt. mfix (λu. (λ(x , y). f (π1 x , π2 y)) (t, u)))= mfix (λt. (λ(x , y). f (π1 x , π2 y)) (t, t)){Equation 2.7}

= mfix (λt. f (π1 t, π2 t))= mfix f
In the last step, we used the fact that (π 1 t, π2 t) = t, which only holds for true products.
To show the correspondence in the other direction, let ∆ x = (x, x), and note that ∆is strict (again thanks to true products). We have:
mfix (λx. f (x , x ))= mfix (f · ∆)= map (π1 · ∆) (mfix (f · ∆))= map π1 (map ∆ (mfix (f · ∆)))

{π1 · ∆ = id }= map π1 (mfix (map ∆ · f )){slide}142

143= map π1 (mfix (λx. mfix (λy. (map ∆ · f ) (π1 x , π2 y))))= map π1 (mfix (λx. mfix (map ∆ · (λy. f (π1 x , y)) · π2 ))){Equation 2.8}

= map π1 (mfix (λx. map ∆ (mfix (λy. f (π1 x , y)))))= map π1 (mfix (map ∆ · (λx. mfix (λy. f (x , y))) · π 1 )){slide}= (map π1 · map ∆) (mfix (λx. mfix (λy. f (x , y)))){slide}= mfix (λx. mfix (λy. f (x , y)))In case of lifted products (Proposition 2.5.4), the proof proceeds similarly. The last
step in the first proof is not applicable, but in that case we can replace the last line withmfix (λ˜ (x , y). f (x , y)), which is predicted by Equation 2.10. The second implication
follows similarly.

B.2

Proposition 2.6.8

mfix (λ(x , y). f x = λz. η (z , h z (x , y)))= mfix (λt. (f · π1 ) t = λz. η (z , h z t))= mfix (λt. (λ(u, v ). (f · π1 ) u = λz. η (z , h z v )) (t, t))= mfix (λx. mfix (λy. (f · π1 ) x = λz. η (z , h z y)))= mfix (λx. (f · π1 ) x = λz. mfix (λy. η (z , h z y))){nest}{left shrink}= mfix (λx. (f · π1 ) x = λz. η (fix (λy. (z , h z y)))){purity}= mfix (λx. (f · π1 ) x = λz. η (z , fix (λy. h z (z , y))))= mfix f = λz. η (z , fix (λy. h z (z , y))){nest (fix )}{pure right}= mfix f = λz. η (fix (λ(x , y). (z , h z (x , y)))){nest (fix )}B.3

Proposition 2.7.1

mfix (λ(x , ). f x = λy. η (q, y))= mfix (map (λy. (q, y)) · f · π1 )= map (λy. (q, y)) (mfix (f · π1 · (λy. (q, y)))){strong sliding}= map (λy. (q, y)) (mfix (λy. f q))= map (λy. (q, y)) (f q){constant functions}= f q = λy. η (q, y)
The need for strong sliding is obvious, since otherwise we would have to require f q =f ⊥ to satisfy the precedent.

144

B.4

Lemma 3.1.4

Recall that η is a natural transformation, that is, it satisfies the equality map h · η α =ηβ · h for all h :: α → β. Assume η is strict at the type α, i.e., η α ⊥α = ⊥m α , and = isstrict in its first argument. Pick an arbitrary type β. We will show that η β ⊥β = ⊥m β :

=

ηβ ⊥β
ηβ (const ⊥β ⊥α )=(ηβ · const ⊥β ) ⊥α==(map (const ⊥β ) · ηα ) ⊥αmap (const ⊥β ) (ηα ⊥α ){naturality of η}=map (const ⊥β ) ⊥m α{assumption: ηα ⊥α = ⊥m α }==⊥m α = η · const ⊥β⊥m β{definition of map }{assumption: = is left-strict}

B.5

Proposition 3.4.2

Given arbitrary f and g, define:h x 1 = f x
h x 2 = g x
We have:
mfix (λx. f x ⊕ g x )= mfix (λx. h x 1 ⊕ h x 2)
= mfix (λx. (η 1 = λy. h x y) ⊕ (η 2 = λy. h x y))= mfix (λx. (η 1 ⊕ η 2) = λy. h x y)
= (η 1 ⊕ η 2) = λy. mfix (λx. h x y)

{Eqn. 3.8}
{left shrink }= mfix (λx. h x 1) ⊕ mfix (λx. h x 2)

{Eqn. 3.8}

= mfix f ⊕ mfix g

B.6

Proposition 4.3.1

We consider each case in turn:
4.5: Right to left implication is immediate. From left to right, fix (f · head ) must be⊥, which only happens when f ⊥ = ⊥. (Note that this establishes the strictness property.)
4.6: Similar to the previous case.
4.7: Simple case analysis.

If mfix f is ⊥, f is strict by 4.5, and both sides re-duce to ⊥. If mfix f is [ ], then f ⊥ = [ ] by 4.6, reducing both sides to ⊥ again.

145

Finally, if mfix f is a cons-cell, the case expression must take its second branch, i.e.,head (mfix f ) = head (fix (f · head)), which is exactly the right hand side by the dinaturality of fix.
4.8: Similar to the previous case, if mfix f equals ⊥ or [ ], both sides yield ⊥. Otherwise,case must take its second branch, i.e., tail (mfix f ) = mfix (tail · f ).
4.9: Consider the test expression for case. We have:fix ((λx. f x : g x ) · head ) = (λx. f x : g x ) (fix (head · (λx. f x : g x )))= (λx. f x : g x ) (fix f )= f (fix f ) : g (fix f )= fix f : g (fix f )Hence, the case expression takes its second branch, yielding:mfix (λx. f x : g x ) = fix f : mfix (tail · (λx. f x : g x ))= fix f : mfix g
4.10: We will use the approximation lemma [7, 38], which states that:(∀n. approx n xs = approx n ys) ⇒ xs = ys
for arbitrary lists xs and ys. The function approx is defined as:
approx:: Integer → [α] → [α]approx 0
xs= ⊥approx (n+1) ⊥= ⊥approx (n+1) [ ]= []approx (n+1) (x :xs) = x : approx n xs
We will prove:∀n.∀f, g. approx n (mfix (λx. f x ++ g x )) = approx n (mfix f ++ mfix g)
by induction on n, implying the required result. Base case (n = 0) is trivial. The induction
hypothesis is:∀f, g. approx k (mfix (λx. f x ++ g x )) = approx k (mfix f ++ mfix g)(B.1)

Note that the hypothesis is assumed for all f and g. This generality will be essential inestablishing the induction step. We need to show:∀f, g. approx (k +1) (mfix (λx. f x ++ g x )) = approx (k +1) (mfix f ++ mfix g)
Pick two arbitrary functions f 0 , g 0 :: α → [α]. It suffices to show that:approx (k+1) (mfix (λx. f 0 x ++ g 0 x )) = approx (k +1) (mfix f 0 ++ mfix g 0 ) (B.2)

146which we establish by case analysis on f 0 ⊥. The cases ⊥ and [ ] are immediate. By 4.5
and 4.6, both sides reduce to ⊥ and approx (k + 1) (mfix g 0 ), respectively. If f 0 ⊥ is acons-cell, it follows that∀x. f 0 x = (head · f 0 ) x : (tail · f 0 ) x(B.3)

Simple inspection of the definition of mfix reveals that mfix f 0 must be a cons-cell in thiscase as well. Hence, we have:
mfix f 0 = head (mfix f 0 ) : tail (mfix f 0 )(B.4)

Therefore,
approx (k +1) (mfix (λx. f 0 x ++ g 0 x ))= approx (k+1) (mfix (λx. ((head · f 0 ) x : (tail · f 0 ) x ) ++ g 0 x ))= approx (k+1) (mfix (λx. (head· f 0) x: ((tail· f 0) x{Eqn. B.3}++ g 0 x )))· f 0)= approx (k+1) (fix (head: mfix (λx. (tail · f 0 ) x ++ g 0 x ))= fix (head · f 0 ) : (approx k (mfix (λx. (tail · f 0 ) x ++ g 0 x ))){Eqn. 4.9}

= fix (head · f 0 ) : (approx k (mfix (tail · f 0 ) ++ mfix g 0 ))= approx (k+1) ((fix (head · f 0 ) : mfix (tail · f 0 )) ++ mfix g 0 ){I.H.}= approx (k+1) ((head (mfix f 0 ) : tail (mfix f 0 )) ++ mfix g 0 ){Eqns. 4.7, 4.8}

= approx (k+1) (mfix

f 0 ++ mfix

g 0)

{Eqn. B.4}

completing the proof.

B.7

Proposition 4.9.1

We need to show that the function mfixErrM satisfies strictness, purity and left shrinking
properties. All cases follow from the corresponding properties of mfixM, and simple symbolic manipulation. We will only present the left shrinking case to illustrate the technique.
To avoid confusion due to overloaded operators, we will write returnM and bindM for themorphisms of m, while returnErrM and bindErrM for those of Err m.
mfixErrM (λx. a ‘bindErrM ‘ λy. f x y)
= {Equation 4.36, expand bindErrM }mfixM (λx. a ‘bindErrM ‘ λy. f (unErr x ) y)
= {expand bindErrM }mfixM (λx. a ‘bindM ‘ λy. case y ofOk q → f (unErr x ) q
Err s → returnM (Err s))

147= {left shrinking on mfixM }a ‘bindM ‘ λy. mfixM (λx. case y ofOk q → f (unErr x ) q
Err s → returnM (Err s))= {Proposition 2.6.2, case is a shortcut for if }a ‘bindM ‘ λy. case y ofOk q → mfixM (λx. f (unErr x ) q)
Err s → mfixM (λx. returnM (Err s))= {fold down mifxErrM on the first branch, Proposition 2.6.1 on the second }a ‘bindM ‘ λy. case y ofOk q → mfixErrM (λx. f x q)
Err s → returnM (Err s)= {fold down bindErrM }a ‘bindErrM ‘ λy. mfixErrM (λx. f x y)

B.8

Proposition 6.3.5

We will need the following two lemmas:
Lemma B.8.1 Let T be a monad and mfix be a value recursion operator satisfying theright shrinking law. Let f : X → T (B × X) and g : B × X → T B 0 . Then,
mfix (λ( , x ). f x = λz. g z = λw. η (w , π2 z ))= mfix (λ( , x ). f x ) = λz. g z = λw. η (w , π 2 z )ProofNote that the first mfix is at instance B 0 × X, while the second is at B × X. We

reason as follows:
mfix (λ( , x ). f x = λz. g z = λw. η (w , π2 z ))= mfix (λ( , x ). f x = λz. g z = λw. η (w , z ) = λ(p, q). η (p, π 2 q))= {slide, λ(p, q). (p, π2 q) is strict}mfix ((λ( , x ). f x = λz. g z = λw. η (w , z )) · (λ(p, q). (p, π 2 q)))= λ(p, q). η (p, π2 q)= mfix (λ( , t). f (π2 t) = λz. g z = λw. η (w , z )) = λ(p, q). η (p, π2 q)= {right shrinking}mfix (λ( , x ). f x ) = λz. g z = λw. η (w , π 2 z )148

The second lemma states a variant of Equation 3.7:
Lemma B.8.2 Let f :: α → m (β, τ ), g :: τ → m α, where m is a commutative monad.
Then,mfix (λt. g (π2 t) = f ) = η · π1
= mfix (λt. f (π2 t) = λt 0 . g (π2 t 0 ) = λa. η (π1 t 0 , a)) = η · π1
provided mfix satisfies strong sliding and nesting.
Proof(Sketch) Note that the first mfix is at instance β × τ , while the second one isat β × α. The proof first extends the recursion to α × (β × τ ), applies commutativity(Proposition 3.3.2), and then gets rid of the τ argument.
To establish Proposition 6.3.5, we need to verify that the definition of trace as given
by 6.21 satisfies Equations 6.14- 6.20. We consider each case in turn:• Left tightening (6.14):
trace (λ(a, x ). g a = λa 0 . f (a 0 , x ))= λa. mfix (λ(b, x ). g a = λa 0 . f (a 0 , x )) = η · π1
= {left shrinking on mfix }λa. g a = λa 0 . mfix (λ(b, x ). f (a 0 , x )) = η · π1
= λa. g a = trace f• Right tightening (6.15):
trace (λ(a, x ). f (a, x ) = λ(b, x ). g b = λb 0 . η (b 0 , x ))= λa. mfix (λ(b, x ). f (a, x ) = λ(b, x ). g b = λb 0 . η (b 0 , x )) = η · π1
= λa. mfix (λ(b, x ). f (a, x ) = λz.(g · π 1 ) z = λb 0 . η (b 0 , π2 z )) = η · π1
= {lemma B.8.1}
λa. mfix (λ(b, x ). f (a, x )) = λz. (g · π 1 ) z= λa. mfix (λ(b, x ). f (a, x )) = λz. η (π 1 z ) = λw. g w= λa. mfix (λ(b, x ). f (a, x )) = η · π1 = g
= λa. trace f a = g
• Sliding (6.16):
trace (λ(a, x ). g x = λx 0 . f (a, x 0 ))= λa. mfix (λ(b, x ). g x = λx 0 . f (a, x 0 )) = η · π1
= λa. mfix (λt. g (π2 t) = curry f a) = η · π1
= {Lemma B.8.2}

149

λa. mfix (λt. curry f a (π2 t) = λt 0 . g (π2 t 0 )= λx 0 . η (π1 t 0 , x 0 )) = η · π1
= λa. mfix (λ(b, x 0 ). f (a, x 0 ) = λ(b, x ). g x
= λx 0 . η (b, x 0 )) = η · π1
= trace (λ(a, x 0 ). f (a, x 0 ) = λ(b, x ). g x = λx 0 . η (b, x 0 ))• Vanishing (6.17):
trace (λ(a, ()). f a = λb. η (b, ()))
= λa. mfix (λ(b, ()). f a = λb. η (b, ())) = η · π 1= {constant functions}λa. f a = λb. η (b, ()) = η · π1
= λa. f a = λb. b= f• Vanishing (6.18): Let
asc (x , (y, z )) = ((x , y), z )iasc ((x , y), z ) = (x , (y, z ))Then,trace (trace (λ((a, x ), y). f (a, (x , y)) = λ(b, (x , y)). η ((b, x ), y)))= trace (trace (λt. f (iasc t) = η · asc))= trace (trace (map asc · f · iasc))= trace (λ(a, x ). mfix (λ(( , ), y). (map asc · f · iasc) ((a, x ), y))= η · π 1 )= trace (λ(a, x ). mfix (map asc · (λ(( , ), y). f (a, (x , y)))) = η · π 1 )= {slide, asc is strict }trace (λ(a, x ). mfix (λ( , ( , y)). f (a, (x , y))) = η · asc = η · π 1 )= λa. mfix (λ( , x ). mfix (λ( , ( , y)). f (a, (x , y))) = η · π 1 · asc)
= η · π1= {slide, π1 · asc is strict }λa. mfix ((λ( , x ). mfix (λ( , ( , y)). f (a, (x , y)))) · π 1 · asc)
= η · (π1 · π1 · asc)
= {π1 · π1 · asc = π1 }λa. mfix (λ( , (x , )). mfix (λ( , ( , y)). f (a, (x , y)))) = η · π 1= {unnest triple}
λa. mfix (λ( , (x , y)). f (a, (x , y))) = η · π 1= trace f

150• Superposing (6.19): Let asc be defined as above,trace (λ((c, a), x ). f (a, x ) = λ(b, x ). η ((c, b), x ))= λ(c, a). mfix (λ(( , ), x ). f (a, x ) = λ(b, x ). η ((c, b), x )) = η · π 1= {slide}λ(c, a). mfix (λ( , ( , x )). f (a, x ) = λ(b, x ). η (c, (b, x )))= η · π1 · asc= {pure right shrinking}
λ(c, a). mfix (λ(b, x ). f (a, x )) = λ(b, x ). η (c, b)= λ(c, a). mfix (λ(b, x ). f (a, x )) = λ(b 0 , x ). η b 0 = λb. η (c, b)= λ(c, a). mfix (λ(b, x ). f (a, x )) = η · π 1 = λb. η (c, b)= λ(c, a). trace f a = λb. η (c, b)• Yanking (6.20):
trace (λ(a, a 0 ). η (a 0 , a))= λa. mfix (λ(b, a 0 ). η (a 0 , a)) = η · π1
= {purity}
λa. η (fix (λ(b, a 0 ). (a 0 , a))) = η · π1
= λa. η (a, a) = η · π1
= ηBibliography(Each entry is followed by a list of page numbers on which the citation appears. All cited
URLs were last accessed in October 2002.)[1] Achten, P., and Peyton Jones, S. Porting the Clean Object I/O Library to
Haskell. In Proceedings of the 12th International Workshop on Implementation of
Functional Languages (2000), pp. 194–213. (98)[2] Barr, M., and Wells, C. Category Theory for Computing Science, second ed.
Prentice Hall International Series in Computer Science. Prentice Hall, 1995. (10, 46,
68, 75, 141)[3] Bekič, H. Programming Languages and their Definition. Selected Papers, vol. 177
of Lecture Notes in Computer Science. Springer Verlag, 1984. (141)[4] Benton, N., Hughes, J., and Moggi, E.
Monads and effects.
Lecture notes from APPSEM’00 Summer School. URL:
www.disi.unige.it/person/MoggiE/APPSEM00/BHM-revised.ps.gz, 2000.
(10,
130)
[5] Benton, N., and Hyland, M. Traced premonoidal categories (Extended Abstract).
In Fixed Points in Computer Science Workshop, FICS’02 (2002). (66, 79, 80, 81,
82, 83, 123, 135)
[6] Bird, R. S. Using circular programs to eliminate multiple traversals of data. Acta
Informatica 21 (1984), 239–250. (122)[7] Bird, R. S. Introduction to Functional Programming using Haskell, second ed. Prentice Hall Europe, London, 1998. (10, 42, 47, 101, 145)
[8] Bjesse, P., Claessen, K., Sheeran, M., and Singh, S. Lava: Hardware designin Haskell. In International Conference on Functional Programming (Baltimore, July
1998). (2)[9] Bloom, S. L., and Ésik, Z. Fixed-point operations on ccc’s. Part I. TheoreticalComputer Science 155, 1 (1996), 1–38. (66, 140)[10] Bloom, S. L., and Ésik, Z. The equational logic of fixed points. TheoreticalComputer Science 179, 1–2 (1997), 1–60. (66, 140)

151

BIBLIOGRAPHY

152[11] Carey, M. J., Chamberlin, D. D., Narayanan, S., Vance, B., Doole, D.,
Rielau, S., Swagerman, R., and Mattos, N. M. O-O, what have they done toDB2? In VLDB’99, Proceedings of 25th International Conference on Very Large Data
Bases, September 7-10, 1999, Edinburgh, Scotland, UK (1999), M. P. Atkinson, M. E.
Orlowska, P. Valduriez, S. B. Zdonik, and M. L. Brodie, Eds., Morgan Kaufmann,
pp. 542–553. (137)[12] Claessen, K. Embedded languages for describing and verifying hardware. PhD
thesis, Chalmers University of Technology, Göteborg, Sweden, 2001. (2, 4, 136)
[13] Claessen, K., and Ljunglöf, P. Typed logical variables in Haskell. In Haskell
Workshop 2000 (2000). (132)[14] Claessen, K., and Sands, D. Observable sharing for functional circuit description.
In Asian Computing Science Conference (1999), pp. 62–73. (136)[15] Cormen, T., Leiserson, C., Rivest, R., and Stein, C. Introduction to Algorithms, second ed. The MIT Press, Cambridge, MA, 2001. (125)[16] de Moor, O. An exercise in polytypic program derivation: repmin. Unpublishedmanuscript. URL: web.comlab.ox.ac.uk/oucl/work/oege.demoor/pubs.htm,
1996. (122)[17] Erkök, L., and Launchbury, J. A recursive do for Haskell: Design and implementation. Tech. Rep. CSE-00-014, Oregon Graduate Institute School of Science andEngineering, Department of CSE, OHSU, August 2000. (88, 95)[18] Erkök, L., and Launchbury, J. Recursive monadic bindings. In Proceedingsof the Fifth ACM SIGPLAN International Conference on Functional Programming,
ICFP’00 (September 2000), ACM Press, pp. 174–185. (6, 125, 135)
[19] Erkök, L., and Launchbury, J. A recursive do for Haskell. In Haskell Workshop’02, Pittsburgh, Pennsylvania, USA (Oct. 2002), ACM Press, pp. 29–37. (84)[20] Erkök, L., Launchbury, J., and Moran, A. Semantics of fixIO. In Fixed Points
in Computer Science Workshop, FICS’01 (September 2001). (90, 98, 121)[21] Erkök, L., Launchbury, J., and Moran, A. Semantics of value recursion formonadic input/output. Journal of Theoretical Informatics and Applications 36, 2(2002), 155–180. (98)[22] Espinosa, D. Semantic Lego. PhD thesis, Columbia University, 1995. (33, 58)[23] Felleisen, M., and Hieb, R. A revised report on the syntactic theories of sequential
control and state. Theoretical Computer Science 103, 2 (1992), 235–271. (104)[24] Filinski, A. Representing monads. In Conference Record of POPL ’94: 21ST ACM
SIGPLAN-SIGACT Symposium on Principles of Programming Languages, Portland,
Oregon (New York, NY, 1994), pp. 446–457. (58)

153

BIBLIOGRAPHY[25] Friedman, D., and Sabry, A. Recursion is a Computational Effect. Tech. Rep.
TR546, Computer Science Department, Indiana University, Dec. 2000. (135)[26] GHC web page. URL: www.haskell.org/ghc. (96)[27] Gordon, A. D. Functional Programming and Input/Output. Distinguished Dissertations in Computer Science. Cambridge University Press, Sept. 1994. (99, 112,
121)[28] Hallgren, T., and Carlsson, M. Programming with fudgets. In Advanced Functional Programming (1995), vol. 925 of Lecture Notes in Computer Science, Springer.
(51)[29] Hallgren, T., and Carlsson, M. Fudgets – Purely Functional Processes withapplications to Graphical User Interfaces. PhD thesis, Chalmers University of Technology, Göteborg, Sweden, 1998. (51, 136)
[30] Hankin, C. Lambda Calculi: A Guide for Computer Scientists, vol. 3 of Graduate
Texts in Computer Science. Clarendon Press, Oxford, 1994. (10)[31] Harper, R., Duba, B. F., and MacQueen, D. B. Typing first-class continuationsin ML. Journal of Functional Programming 3, 4 (October 1993), 465–484. Also in
ACM POPL 91, pp. 163–173. (58, 62)[32] Hasegawa, M. Recursion from cyclic sharing: Traced monoidal categories andmodels of cyclic lambda calculi. In Typed Lambda Calculus and Applications (1997),
pp. 196–213. (66, 71, 134)[33] Hasegawa, M. Models of Sharing Graphs, A categorical semantics of let and letrec.
Distinguished Dissertations in Computer Science. Springer Verlag, 1999. (66, 67, 68,
69, 70, 78, 134, 141)[34] Hughes,
J.
Global variables in Haskell.
www.cs.chalmers.se/~rjmh/Globals.ps. (89)Draft

paper.

URL:[35] Hughes, J. Why functional programming matters. Computer Journal 32, 2 (1989),
98–107. (10)[36] Hughes, J. Generalising monads to arrows. Science of Computer Programming 37,
1-3 (May 2000), 67–111. (79, 135)
[37] Hugs (Haskell Users Gofer System) web page. URL: www.haskell.org/hugs. (96)[38] Hutton, G., and Gibbons, J. The generic approximation lemma. Information
Processing Letters 79, 4 (2001), 197–201. (50, 145)
[39] Jeffrey, A. Premonoidal categories and a graphical view of programs. Unpublishedmanuscript. URL: fpl.cs.depaul.edu/ajeffrey/premon/paper.html, 1997. (79)BIBLIOGRAPHY

154[40] Jones, M. P. First-class polymorphism with type inference. In Proceedings of theTwenty Fourth ACM SIGPLAN-SIGACT Symposium on Principles of Programming
Languages (POPL ’97) (1997). (88)[41] Jones, M. P. Typing Haskell in Haskell. In Proceedings of the 1999 Haskell Workshop(1999). (95, 101)[42] Jones, M. P., and Duponcheel, L.
Composing monads.
Tech. Rep.
YALEU/DCS/RR-1004, Department of Computer Science, Yale University, Dec.
1993. (33, 49)[43] Joyal, A., Street, R. H., and Verity, D. Traced monoidal categories. Mathematical Proceedings of the Cambridge Philosophical Society 119, 3 (1996), 447–468.
(66, 68, 69, 70)[44] Kelsey, R., Clinger, W., and Rees, J. (Editors.) Revised 5 report on the algorithmic language Scheme. ACM SIGPLAN Notices 33, 9 (Sept. 1998), 26–76. (58,63)[45] King, D. J., and Launchbury, J. Structuring depth-first search algorithms in
Haskell. In Conference Record of POPL ’95: 22nd Annual ACM SIGPLAN-SIGACT
Symposium on Principles of Programming Languages (1995), pp. 344–354. (130)[46] King, D. J., and Wadler, P. Combining monads. In Glasgow Workshop on
Functional Programming (Ayr, July 1992), J. Launchbury and P. M. Sansom, Eds.,
Springer Verlag. (28)[47] Launchbury, J. Lazy imperative programming. In Proceedings of the ACM SIGPLAN Workshop on State in Programming Languages, Copenhagen, DK, SIPL ’92(1993), pp. 46–56. (2)[48] Launchbury, J. A natural semantics for lazy evaluation. In Conference record of
the Twentieth Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, Charleston, South Carolina (1993), pp. 144–154. (99, 108,109)[49] Launchbury, J., Lewis, J., and Cook, B. On embedding a microarchitecturaldesign language within Haskell. In Proceedings of the ACM SIGPLAN International
Conference on Functional Programming (ICFP ’99) (1999), pp. 60–69. (2, 4, 85)[50] Launchbury, J., and Paterson, R. Parametricity and unboxing with unpointed
types. In European Symposium of Programming (Apr. 1996), vol. 1058 of Lecture
Notes in Computer Science, Springer, pp. 204–218. (10, 20)[51] Launchbury, J., and Peyton Jones, S. L. Lazy functional state threads. ACM
SIGPLAN Notices 29, 6 (June 1994), 24–35. (42)[52] Launchbury, J., and Peyton Jones, S. L. State in Haskell. Lisp and Symbolic
Computation 8, 4 (Dec. 1995), 293–341. (42, 121, 128)

BIBLIOGRAPHY

155[53] Liang, S. Modular Monadic Semantics and Compilation. PhD thesis, Yale University, 1998. (1, 31, 33, 53, 54, 55, 61)[54] Liang, S., Hudak, P., and Jones, M. P. Monad transformers and modular
interpreters. In Conference record of POPL ’95, 22nd ACM SIGPLAN-SIGACT
Symposium on Principles of Programming Languages: San Francisco, California,
January 22–25, 1995 (1995), ACM, Ed., ACM Press, pp. 333–343. (33)[55] MacLane, S. Categories for the Working Mathematician, second ed., vol. 5 of
Graduate Texts in Mathematics. Springer Verlag, 1997. (10, 28, 68, 74, 140)[56] Marlow, S., Peyton Jones, S. L., Moran, A., and Reppy, J. Asynchronous
exceptions in Haskell. In ACM SIGPLAN 2001 Conference on Programming Language
Design and Implementation (PLDI) (Snowbird, Utah, June 20–22 2001). (121)[57] Mason, I. A., and Talcott, C. L. Equivalence in functional languages witheffects. Journal of Functional Programming 1, 3 (1991), 287–327. (112)[58] Matthews, J. Algebraic Specification and Verification of Processor Microarchitectures. PhD thesis, Oregon Graduate Institute of Science and Technology, Portland,
Oregon, 2000. (2)[59] Matthews, J., Cook, B., and Launchbury, J. Microprocessor specification in
Hawk. In Proceedings of the 1998 International Conference on Computer Languages(1998), IEEE Computer Society Press, pp. 90–101. (2)[60] Meijer, E., Fokkinga, M., and Paterson, R. Functional programming withbananas, lenses, envelopes and barbed wire. In Proceedings 5th ACM Conf. on Functional Programming Languages and Computer Architecture, FPCA’91, Cambridge,
MA, USA, 26–30 Aug 1991, J. Hughes, Ed., vol. 523 of Lecture Notes in Computer
Science. Springer-Verlag, Berlin, 1991, pp. 124–144. (20)[61] Milner, R. Communicating and Mobile Systems: The π-Calculus. Cambridge University Press, May 1999. (99, 121)[62] Moggi, E. An abstract view of programming languages. Tech. Rep. ECS-LFCS-90113, Dept. of Computer Science, Edinburgh Univ., 1990. (1)[63] Moggi, E. Notions of computation and monads. Information and Computation 93,
1 (1991). (1, 8, 10, 67)[64] Mosses, P. D. Semantics, modularity, and rewriting logic. In Electronic Notes in
Theoretical Computer Science (2000), C. Kirchner and H. Kirchner, Eds., vol. 15,
Elsevier Science Publishers. (1)[65] Nordlander, J. Reactive Objects and Functional Programming. PhD thesis,
Chalmers University of Technology, Göteborg, Sweden, 1999. (96, 130, 136)

156

BIBLIOGRAPHY[66] Paterson, R. A new notation for arrows. In Proceedings of the Sixth ACM SIGPLAN International Conference on Functional Programming, ICFP’01, Florence,
Italy (September 2001), ACM Press, pp. 229–240. (66, 79, 80, 83, 96, 135)
[67] Peyton Jones, S. L. Tackling the awkward squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell. In Engineering theories
of software construction (2001), T. Hoare, M. Broy, and R. Steinbruggen, Eds., IOS
Press, pp. 47–96. (98, 99, 108, 121)
[68] Peyton Jones, S. L., and Hughes, J. (Editors.) Report on the programming
language Haskell 98, a non-strict purely-functional programming language. URL:
www.haskell.org/onlinereport, Feb. 1999. (2, 10, 14, 20, 30, 86, 87, 89, 95, 98,
101, 101)[69] Peyton Jones, S. L., and Wadler, P. Imperative functional programming. In
Conference record of the Twentieth Annual ACM SIGPLAN-SIGACT Symposium on
Principles of Programming Languages, Charleston, South Carolina (1993), pp. 71–84.
(2, 98)[70] Pierce, B. C. Basic Category Theory for Computer Scientists. MIT Press, Cambridge, MA, 1991. (141)[71] Pitts, A. M. Parametric polymorphism and operational equivalence. Mathematical
Structures in Computer Science 10 (2000), 321–359. (112, 121)[72] Power, J., and Robinson, E. Premonoidal categories and notions of computation.
Mathematical Structures in Computer Science 7, 5 (1997), 453–468. (81)[73] Power, J., and Thielecke, H. Closed Freyd- and κ-categories. In Automata,
Languages and Programming (1999), pp. 625–634. (79, 82)[74] Recursive
monadic
bindings
webwww.cse.ogi.edu/PacSoft/projects/rmb. (96)

page.

URL:

[75] Reynolds, J. C. Types, abstraction, and parametric polymorphism. In Information
Processing’83, R. Mason, Ed. North-Holland, Amsterdam, 1983, pp. 513–523. (10,20)[76] Reynolds, J. C. Theories of Programming Languages. Cambridge University Press,
1998. (1, 10, 12, 140)[77] Schmidt, D. A. Denotational Semantics. Allyn and Bacon, Boston, 1986. (1, 10,32)[78] Sestoft, P. Deriving a lazy abstract machine. Journal of Functional Programming
7, 3 (1997), 231–264. (109)[79] Simpson, A., and Plotkin, G. Complete axioms for categorical fixed-point operators. In Proceedings of the Fifteenth Annual IEEE Symposium on Logic in Computer
Science (2000), pp. 30–41. (20, 66, 141)

BIBLIOGRAPHY

157[80] Simpson, A. K. A characterisation of the least-fixed-point operator by dinaturality.
Theoretical Computer Science 118, 2 (1993), 301–314. (140)[81] Smyth, M., and Plotkin, G. The category-theoretic solution to recursive domain
equations, 1982. (140)[82] Stoy, J. E. Denotational Semantics: The Scott-Strachey Approach to Programming
Language Theory. MIT Press, Cambridge, Massachusetts, 1977. (1, 10, 20)[83] Tennent, R. D. Semantics of Programming Languages. Prentice Hall, New York,1991. (1, 10, 12)[84] Thielecke, H. Using a continuation twice and its implications for the expressive
power of call/cc. Higher-Order and Symbolic Computation 12, 1 (1999), 47–74. (58,62)[85] Thiemann, P.
WASH/CGI: Server-side web scripting with sessions.
compositional forms, and graphics.
Unpublished manuscript. URL:
www.informatik.uni-freiburg.de/~thiemann/papers, Mar. 2001. (127)[86] Turbak, F., and Wells, J. B. Cycle therapy: A prescription for fold and unfold on
regular trees. In Proc. 3rd Int’l Conf. Principles & Practice Declarative Programming,
PPDP’01 (Sept. 2001). (137)[87] Turner, D. A. A new implementation technique for applicative languages. Software
Practice and Experience 9, 1 (Jan. 1979), 31–49. (10)[88] Wadler, P. Theorems for free! In FPCA’89, London, England. ACM Press, Sept.
1989, pp. 347–359. (10, 20)[89] Wadler, P. Comprehending Monads. In LISP’90, Nice, France. ACM Press, 1990,
pp. 61–78. (2, 31, 125)[90] Wadler, P. Monads and composable continuations. Lisp and Symbolic Computation
7, 1 (Jan. 1994), 39–56. (58)[91] Wadler, P. Monads for functional programming. In Advanced Functional Programming, J. Jeuring and E. Meijer, Eds., vol. 925 of Lecture Notes in Computer Science.
Springer Verlag, 1995. (2, 10, 42, 47)[92] Winskel, G. The Formal Semantics of Programming Languages: An Introduction.
Foundations of Computing series. MIT Press, Feb. 1993. (140, 141)[93] Wright, A. K. Simple imperative polymorphism. Lisp and Symbolic Computation
8, 4 (1995), 343–355. (95, 96)

158

INDEX

Index
Achten, P., 98
additive monad, 25, 30
list, 30
maybe, 30
arrows, 79, 96, 135, 136
Bekič property, 15, 16, 141
Benton, N., 66, 79–81, 83, 123, 130,
135
black hole, 102, 107–109, 113
Carlsson, M., 51, 61, 136
category
CCC, 73
Freyd, 79, 82
Kleisli, 67, 72, 73, 75, 81
premonoidal, 81, 82, 135
symmetric monoidal, 68, 72, 75,
134
symmetric premonoidal, 81
traced symmetric monoidal, 69
traced symmetric premonoidal, 82
Claessen, K., 4, 132, 136
commutative monad, 15, 72, 74, 134
commutativity, 25, 29
environment, 49
identity, 36
maybe, 37
continuation, 58, 138
callcc, 59, 62, 63
CPS, 58
first-class, 58, 62–65
continuity, 18, 78
derivation, 112
effect of, 112
silent, 114
dinaturality, 14, 140
distributivity, 30

list, 41
maybe, 38
environment monad, 28, 31, 48, 49,
57, 73, 83, 127
commutativity, 49
embedding into state, 49
idempotency, 49
left shrinking, 49
nesting, 49
purity, 49
right shrinking, 49
strictness, 49
strong sliding, 49
Espinosa, D., 58
execution context, 104
empty, 104
Filinski, A., 65
fixIO, 90, 98–100, 113, 118, 123
free theorem, 20
Friedman, D., 135
fudget monad, 51, 100
left shrinking, 53
nesting, 53
purity, 53
right shrinking, 53
sliding, 53
strictness, 53
strong sliding, 53
Gordon, A. D., 99
Hallgren, T., 51, 136
Hasegawa, M., 66, 68–70, 78, 134
heap, 102
slice, 103
Hughes, J., 79, 89, 130, 131
Hyland, M., 66, 70, 79–81, 83, 123,
135

INDEX

idempotency, 25, 28
environment, 49
identity, 36
maybe, 37
identity monad, 28, 31, 35, 49, 57, 61,83
commutativity, 36
idempotency, 36
left shrinking, 35
nesting, 36
purity, 35
right shrinking, 36
strictness, 35
strong sliding, 36
injection, 21
input stream, 102
empty, 103
IO monad, 9, 25, 27, 51, 83, 90, 98,
99, 123⊥, 114left shrinking, 120
nesting, 121
purity, 119
right shrinking, 27, 121
sliding, 121
strictness, 118
strong sliding, 121
Jeffrey, A., 79
Joyal, A., 66, 68
Launchbury, J., 4, 85, 99, 108, 109,
121
left shrinking, 14, 17, 18, 23, 24, 30,
31, 94, 99, 120
continuations, 63
environment, 49
fudgets, 53
IO, 120
list, 40
maybe, 37
output, 48
state, 45
tree, 50
Liang, S., 53

159

list monad, 9, 25, 27, 30, 31, 38, 50,
83, 124
distributivity, 41
left shrinking, 40, 125
nesting, 41
purity, 40
right shrinking, 27, 41
sliding, 41
strictness, 40strong sliding, 41
Ljunglöf, P., 132
logical variables, 130
Maier, D., 137
maybe monad, 9, 25, 27, 28, 30, 31,
36, 54, 83
commutativity, 37
distributivity, 38
idempotency, 37
left shrinking, 37
nesting, 37
purity, 37
right shrinking, 27, 37
strictness, 37
strong sliding, 37
mdo-notation, 8, 86, 136
defined variables, 91
dependent generators, 91
let bindings, 87
MonadFix class, 7, 95, 97
naive translation, 8, 86
recursive variables, 91
segmentation, 89
segments, 92
shadowing, 90
type checking, 95
used variables, 91
mirror image (of a property), 21
Moggi, E., 1, 2, 8, 10, 130
monad embedding, 31
environment into state, 49
identity into any other, 36
maybe into list, 32, 41
output into state, 47
monad homomorphism, 31

160

INDEX

monad transformers, 33, 53
continuations, 34, 53, 61, 131environments, 34, 54
error, 34, 53
state, 34, 55
monoid, 46, 75
commutative, 72, 75
representation monad, 46
monotonicity, 18
nesting, 15
environment, 49
fudgets, 53
IO, 121
list, 41
maybe, 37
output, 48
state, 45
tree, 51
Nordlander, J., 96, 130, 136
output monad, 31, 46, 126
embedding into state, 47
left shrinking, 48
nesting, 48
pure right shrinking, 48
purity, 48
right shrinking, 48
strictness, 48
parametricity, 10, 139of mfix, 20of the IO language, 121
Paterson, R., 15, 26, 66, 79, 80, 83,
96, 135
Peyton Jones, S. L., 98, 99, 108, 121
Pitts, A. M., 121
Plotkin, G. D., 20
Power, J., 79, 81
program state, 103
closed, 104
divergent, 112
normal, 112
stuck, 112
terminal, 104, 112

type of, 104
pure right shrinking, 18
purity, 13, 17, 18, 23, 24, 31, 80, 99,
119continuations, 60
environment, 49
fudgets, 53
IO, 119
list, 40
maybe, 37
output, 48
state, 45
tree, 50right shrinking, 22, 25, 27, 86, 87, 90
environment, 49
fudgets, 53
IO, 121
list, 41
maybe, 37
output, 48
state, 46
tree, 51
Robinson, J., 81
Sabry, A., 62, 135
Sands, D., 136
scope change, 19, 23
segment, 92
exported variables, 92
free variables, 92
recursive, 93
Sestoft, P., 109
Simpson, A., 20
state monad, 9, 27, 31, 42, 83
left shrinking, 45
nesting, 45
purity, 45
right shrinking, 27, 46
sliding, 45
strictness, 44strong sliding, 46
strictness, 12, 18, 23, 24, 31, 118
environment, 49
fudgets, 53

INDEX

IO, 118
list, 40
maybe, 37
output, 48
state, 44
tree, 50
strong sliding, 22, 25–27
environment, 49
fudgets, 53
IO, 121
list, 41
maybe, 37
state, 45, 46
tree, 51
term, 101
IO, 102, 113
pure, 102, 104, 113
state, 103
Thielecke, H., 58, 62, 79
Thiemann, P., 16, 127
tree monad, 49
left shrinking, 50
nesting, 51
purity, 50
right shrinking, 51
sliding, 51
strictness, 50
strong sliding, 51
Turbak, F., 137
uniformity, 20
Wadler, P., 2, 58, 98
Wells, J. B., 137

161

Biographical Note
Levent Erkök was born in 1972, Merzifon, Turkey. He received a B.S. degree in Computer
Engineering from the Middle East Technical University in Ankara, Turkey, in 1994, andan M.S. degree in Computer Science from the University of Texas at Austin in 1998.
He started his Ph.D. studies at Oregon Graduate Institute later that year. His researchinterests lie in the theory of programming languages, especially the functional paradigm.

162