Primitive (Co)Recursion is a wonderful and illuminating paper, but it is dense in its concepts for those unfamiliar with category theory, and uses the semiscrutable bracket syntax introduced by Bananas. But there's no need for alarm if category theory isn't your cup of tea: Haskell allows us, once again, to express elegantly the new recursion schemes defined in Primitive (Co)Recursion. Guided by Uustalu and Vene's work, we'll derive these two new recursion schemes and explore their ways in which they simplify complicated folds and unfolds. Though these new morphisms are, definitionwise, simple variations on paramorphisms and apomorphisms, in practice they provide surprising power and clarity, as Uustalu and Vene assert:
[We] argue that even these schemes are helpful for a declaratively thinking programmer and program reasoner who loves languages of programming and program reasoning where programs and proofs of properties of programs are easy to write and read.
That sure sounds like us. Let's get going.
In our first entry, we defined Term
, the fixedpoint of a Haskell Functor
, with an In
constructor that wraps one level of a structure and an out
destructor to perform the corresponding unwrap^{1}.
newtype Term f = In { out :: f (Term f) }
Given an algebra — a folding function that collapses a Functor f
containing a
's into a single a
—
type Algebra f a = f a > a
we use the catamorphism cata
to apply a leaftoroot^{2} fold over any recursivelydefined data structure. cata
travels to the most deeplynested point in the data structure by fmap
ing itself, recursively, into the next level of the stucture. When fmap cata x
returns an unchanged x
, we cease recursing (because we have hit the mostdeeplynested point); we can then begin constructing the return value by passing each node to the algebra, leaftoroot, until all the recursive invocations have finished.
cata :: (Functor f) => Algebra f a > Term f > a
cata f = out >>> fmap (cata f) >>> f
But the catamorphism has its limits: as it is applied to each level of the structure, it can only examine the current carrier value from which it is building. Given the Falgebra f a > a
, each of the structure's children—the a
values contained in the f
container—has already been transformed, thus losing information about the original structure. To remedy this, we introduced para
, the paramorphism, and an Ralgebra to carry the original structure with the accumulator:
type RAlgebra f a = f (Term f, a) > a
para :: Functor f => RAlgebra f a > Term f > a
para f = out >>> fmap (id &&& para f) >>> f
Paramorphisms allow us, at each stage of the fold, to view the original structure of the examined node before the fold began. Though this is more powerful than the catamorphism, in many cases it does not go far enough: many useful functions are defined not just in terms of the original argument to the function, but in terms of previous computed values. The classic^{3} example is the Fibonacci function, the general case of which is defined in terms of two previous invocations:
fib :: Int > Int
fib 0 = 0
fib 1 = 1
fib n = fib (n1) + fib (n2)
We could express this function using a catamorphism—though one of the carrier values (fib (n1)
) would be preserved, as the accumulator of our fold, we would need another explicit recursive call to cata
to determine the historical value of fib (n2)
. This is a bummer, both in terms of efficiency—we're recalculating values we've already calculated—and in terms of beauty: a function so fundamental as fib
deserves a better implementation, especially given the expressive power of recursion schemes.
The imperative programmers among us will have a solution to this inefficiency: "iterate!", they will yell, or perhaps they will clamor "introduce a cache!" in a great and terrible voice. And it's true: we could compute fib
with a forloop or by memoizing the recursive call. But the former approach entails mutable state—a very big can of worms to open for such a simple problem—and the latter leaves us with two problems. Uustalu and Vene's histomorphism provides a way out: we will preserve the history of the values our fold computes, so that further recursive calls to compute past values become unnecessary. This style of recursion is called courseofvalue recursion, since we record the values evaluated as our fold function courses through the structure.
Rather than operate on an f a
, a data structure in the process of being folded, we'll operate on a more sophisticated structure, so that the argument to our fold function contains the history of all applications of the fold itself. Instead of a just a carrier value a
, our f
will contain a carrier value and a recursive, unrollable record of past invocations, to wit:
data Attr f a = Attr
{ attribute :: a
, hole :: f (Attr f a)
}
We'll call this Attr
, since it's an 'attributed' version of Term
.
An Attr f a
contains an a
—a carrier value, storing the inprogress value of the fold—as well as a fixedpoint value (analogous to Term
) at each level of recursion. Thanks to the fixedpoint hole
within the f
, further Attr
items are preserved, each of which contains the shape of the folded functor f
. And within the f
there lie further Attr
values, each of which contains a carrier value yielded by their application in their attribute
slot. And those Attr
values in turn contain further hole
s, which contain the historical records pertaining to their childrens' history, and so on and so forth until the bottom of the data structure has been reached. As such, the entire history of the fold is accessible to us: the holes
preserve the shape of the data structure (which was lost during cata
), and the attribute
holds the record of applying the fold to each entity in said data structure.
We have a word for preserving a record of the past, of course—history^{4}. A fold operation that uses Attr
to provide both an accumulator and a record of prior invocations is known as a histomorphism—a shapechanging (morpho) fold with access to its history (histo).
Let's define the histomorphism. It will, like its cousins cata
and para
, use an algebra for its fold function. But unlike the Falgebra of cata
or the Ralgebra of para
, we'll be using an algebra that operates on an Attr f a
, yielding an a
out of it. We call this a courseofvalue algebra, abbreviated to a CValgebra, and define a type alias for it, so we end up with a more comprehensible type signature in the histomorphism:
type CVAlgebra f a = f (Attr f a) > a
That is, a CValgebra maps from a container f
containing children of type Attr f a
(which in turn contain f (Attr f a)
children, as far down as is needed in the nested structure), to a final result type a
. The shape of the folded structure and the history of its applications are all contained in its Attr
values: all you have to do is unroll the hole
value to go back one level in history and use attribute
to examine the stored value.
Our histo
function will be similar to cata
and para
at its heart. We start by unpacking the Term
—the initial argument must be a Term
rather than an Attr
, since as we haven't started the fold yet we have no value to fill in for attribute
. We will then recurse, with fmap
, into the thusrevealed structure until we hit its root. We then use the CValgebra to build the value, starting at the root and continuing upwards to the topmost leaf. These steps are analogous to how we defined cata
and para
, so let's start defining it:
histo :: Functor f => CVAlgebra f a > Term f > a
histo h = out >>> fmap worker >>> h
But what type should the worker have? Well, we can ask GHC, thanks to one of its most useful features^{5}—type holes. By prepending an underscore to the use of worker
, we can allow the program compilation to continue as far as is possible—however, when the compilation process has finished, GHC will remind us where we used a type hole, and inform us of the type signature it inferred for _worker
. (As a fulltime Haskell programmer, I use this feature nearly every day.)
histo :: Functor f => CVAlgebra f a > Term f > a
histo h = out >>> fmap _worker >>> h
Running this code in GHC yields the following typehole message:
/Users/patrick/src/morphisms/src/Main.hs:14:24: error:
• Found hole: ‘_worker’ with type :: Term f > Attr f a
Okay, that makes sense! We're operating on Term f
values (lifted into this context by the fmap
within histo
), and we need to yield an Attr f a
, so that the outside Term f
can be transformed into an f (Attr f a)
and then passed into the CValgebra.
An Attr f a
, as defined above, contains two values: a plain a
type, and a recursive f (Attr f a)
hole. Given a Term f
and our ability to invoke both histo
and worker
recursively, we can build the Attr f a
we need. Let's start by defining the skeleton of worker
: given a Term f
, called t
, it constructs an Attr
, containing two fields.
worker t = Attr _ _
The first field, the a
, is yielded by recursing with histo
on the provided Term
—easy enough. This is just like the catamorphism—indeed, a catamorphism is a histomorphism that ignores the provided history.
worker t = Attr (histo h term) _
The second field's construction is more clever: we unwrap term
with the out
function, which gives us an f (Term f)
out of a Term f
. Since we don't know exactly what type f
is yet, we can't extract the contained Term f
—but we can operate on it, with fmap
, provided by the Functor
constraint. So, to go from an f (Term f)
to an f (Attr f a)
, we need a function of type Term f > Attr f a
… hang on, that's just worker
itself!
worker t = Attr (histo h t) (fmap worker (out t))
This is the heart of histo
's elegance: it's 'doubly recursive', in that its worker
function invokes both histo
and worker
itself.
Now we have a working histo
function:
histo :: Functor f => CVAlgebra f a > Term f > a
histo h = out >>> fmap worker >>> h where
worker t = Attr (histo h t) (fmap worker (out t))
This shit is wild. But what does it mean? We've filled in all these type holes, and we have a working histo
function, but why does it work? Why does this preserve the history?
The answer lies in that second expression in worker
, the recursive invocation of fmap
. If we omitted that expression, we would have a function equivalent to cata
—one that throws all its intermediate variables away while computing the result of a fold. But fmap worker (out t)
ensures that the result computed by worker
's recursive call into t
by histo
is not lost: worker
modifies t
with the result computed by worker
itself, and caches that updated result by storing it in the second field of Attr
. As we flow, roottoleaf, upwards through the data structure, we construct a new Attr
value, which in turn contains the previous result, which itself preserves the result before that, and so on. Each step yields an uptodate snapshot of what we have computed in the past.
By not throwing out intermediate results, and pairing these intermediate results with the values used to calculate them, we automatically generate and update a cache for our fold.
Now, I may have used fib
as an example of a courseofvalue recursive function, but I won't provide an example of using histo
to calculate the nth Fibonacci number (though it's a good exercise). Let's solve a toy problem that's slightly more interesting, one that histomorphisms make clear and pure, and one whose solution can be generalized to all other problems of its ilk.
The changemaking problem is simple: given a monetary amount N
, and a set of denominations (penny, nickel, dime, &c.), how many ways can you make change for N
? While it's possible to write a naïve recursive solution for this problem, it becomes intolerably slow for large values of N
: each computation for N
entails computing the values for N  1
, and N  2
, and N  3
, and so forth: if we don't store these intermediate amounts in a cache, we will waste our precious time on this earth. And, though this era may be grim as all hell, slow algorithms are no way to pass the time.
We'll start by setting up a list of standard denominations. Feel free to adjust this based on the denominational amounts of your country of residence.
type Cent = Int
coins :: [Cent]
coins = [50, 25, 10, 5, 1]
So our fundamental procedure is a function change
, that takes a cent amount and returns a count of how many ways we can make change for said cent amount:
change :: Cent > Int
It is here where we hit our first serious roadblock. I asserted earlier that the changemaking problem, and all the other knapsack problems of its ilk, are soluble with a histomorphism—a cached fold over some sort of data structure. But here we're dealing with… naturalnumber values. There are no lists, no vectors, no rose trees—nothing mappable (that is to say, nothing with a Functor
instance) and therefore nothing to fold over. What are we supposed to do?
All is not lost: we can fold over the natural numbers, just as we would fold over a list. We just have to define the integers in an unconventional, but simple, way: every natural number is either zero, or 1 + the previous. We'll call this formulation of the natural numbers Nat
— the zero value will be Zero
^{6}, and the notion of the subsequent number Next
. Put another way, we need to encode Peano numerals in Haskell^{7}.
data Nat a
= Zero
 Next a
deriving Functor
We use Term
to parameterize Nat
in terms of itself—that is to say, given Term
, we can stuff a Nat
into it so as to represent an arbitrarilynested hierarchy of contained Nat
s, and thus represent all the natural numbers:
one, two, three :: Term Nat
one = In (Next (In Zero))
two = In (Next one)
three = In (Next two)
For convenience's sake, we'll define functions that convert from standard Int
values to foldable Term Nat
s, and vice versa. Again, these do not look particularly efficient, but please give me the benefit of the doubt.
 Convert from a natural number to its foldable equivalent, and vice versa.
expand :: Int > Term Nat
expand 0 = In Zero
expand n = In (Next (expand (n  1)))
compress :: Nat (Attr Nat a) > Int
compress Zero = 0
compress (Next (Attr _ x)) = 1 + compress x
While this is, at a glance, obviously lessefficient than using integers, it's not as bad as it seems. We only have three operations: increment, converting from zero, and converting to zero. Restricting our operations to these—rather than writing our own code for addition or subtraction, both of which are lineartime over the Peano numerals—means that operations on our Term Nat
types are almost the same as hardwaretime costs, barring GHCspecific operations. As such, the expressivity we yield with our foldable numbers is well worth the very slight costs.
Given an amount (amt
), we solve the changemaking problem by converting that amount to a Term Nat
with expand
, then invoking histo
on it with a provided CValgebra—let's call it go
. We'll define it in a whereclause below.
change :: Cent > Int
change amt = histo go (expand amt) where
Since we're operating on foldable natural values (Nat
) and ultimately yielding an integral result (the number of ways it is possible to make change for a given Nat
), we know that our CValgebra will have as its carrier functor Nat
and its result type Int
.
 equivalent to Nat (Attr Nat Int) > Int
go :: CVAlgebra Nat Int
Because histo
applies its algebra from leaftoroot, it starts at the deepest nested position in the Term Nat
—that is to say, Zero
. We know that there's only one way to make change for zero coins—by giving zero coins back—so we encode our base case by explicitly matching on a Zero and returning 1.
go Zero = 1
Now comes the interesting part—we have to match on Next
. Contained in that Next
value will be an Attr Nat Int
(which we'll refer to as attr
), containing the value yielded from applying go
to the previous Nat
ural number. Since we'll need to feed this function into compress
to perform actual numeric operations on it (since we did not write the requisite boilerplate to make Nat
an instance of the Num
typeclass^{8}), we'll use an @pattern to capture it under the name curr
.
go curr@(Next attr) = let
Because we need to find out what numeric amounts (from coins
) are valid changecomponents for curr
, we have to get an Int
out of curr
. We'll call this value given
, since it's our given amount.
given = compress curr
Now we have to look at each value of the coins
list. Any values greater than given
are right out: you can't use a quarter to make change for a dime, obviously.
validCoins = filter (<= given) coins
Now we subtract the given
amount from each element of validCoins
. This list represents, for each coin in validCoins
, how much change we have remaining after using that coin to make change for given
—if given
were equal to 10, the list would be [9, 5, 0]
.
remaining = map (given ) filtered
Now we have to consider each element of the list. If it's zero, then we've successfully made change. Hooray! Let's count how many zeroes there are in the remaining
list.
zeroCount = length (filter (== 0) remaining)
If it's not zero, then we have to consider how many ways we could make change out of that number—but, since we know that that we've already calculated that result, because it's by definition less than given
! So all we have to do is look up the cached result in our attr
. (We'll implement the lookup
function later on—it is two lines of code.) We'll add all these cached results together with sum
.
others = sum (map (lookup attr) remaining)
Then all that's left to do is add zeroCount
and others
together.
in zeroCount + others
Let's take a look at what we've written so far.
change :: Cent > Int
change amt = histo go (expand amt) where
go :: Nat (Attr Nat Int) > Int
go Zero = 1
go curr@(Next attr) = let
given = compress curr
validCoins = filter (<= given) coins
remaining = map (given ) validCoins
zeroCount = length (filter (== 0) remaining)
results = sum (map (lookup attr) remaining)
in zeroCount + results
Wow. This is pretty incredible. Not only do we have a simple, pure, concise, and performant solution to the changemaking problem, but the caching is implicit: we don't have to update the cache ourselves, because histo
does it for us. We've stripped away the artifacts required to solve this problem efficiently and zeroed in on the essence of the problem. This is remarkable.
I told you I would show you how to look up the cached values, and indeed I will do so now. An Attr Nat a
is essentially a nonempty list: if we could pluck the mostfinal Attr Nat a
after change
has finished executing, we would see the value of change 0
stored inside the first attribute
value, the value of change 1
stored inside the attribute
within the first attribute's hole
, and the value for change 2
inside that further hole
. So, given an index parameter n
, we return the attribute
if n
is 0, and we recurse inside the hole
if not, with n  1
.
lookup :: Attr Nat a > Int > a
lookup cache 0 = attribute cache
lookup cache n = lookup inner (n  1) where (Next inner) = hole cache
Something crucial to note is that the fixedpoint accumulator—the f (Attr f a)
parameter to our CValgebra—changes shape based on the functor f
contained therein. Given an inductive functor Nat
that defines the natural numbers, Nat (Attr Nat a)
is isomorphic to []
, the ordinary linked list: a Zero
is the empty list, and a Next
that contains a value (stored in Attr
's attribute
field) and a pointer to the next element of the list (stored in the hole :: Nat (Attr Nat a))
field in the given Attr
). This is why our implementation of lookup
is isomorphic to an implementation of !!
over []
—because they're the same thing.
But what if we use a different Functor
inside an Attr
? Well, then the shape of the resulting Attr
changes. If we provide the list type—[]
—we yield Attr [] a
, which is isomorphic to a rose tree—in Haskell terms, a Tree a
. If we use Either b
, then Attr (Either b) a
is a nonempty list of computational steps, terminating in some b
value. Attr
is more than an "attributed Term
"—it is an adaptive cache for a fold over any type of data structure. And that is truly wild.
As with para
, the increased power of histo
allows us to express cata
with new vocabulary. Every Falgebra can be converted into a CValgebra—all that's needed is to ignore the hole
values in the contained Functor f
. We do this by mapping attribute
over the functor before passing it to the Falgebra, throwing away the history contained in hole
.
cata :: Functor f => Algebra f a > Term f > a
cata f = histo (fmap attribute >>> f)
Similarly, we can express para
with histo
, except instead of just fmapping with attribute
we need to do a little syntactic juggling to convert an f (Attr f a)
into an f (Term f, a)
. (Such juggling is why papers tend to use bananabracket notation: implementing this in an actual programming language often requires syntactic noise such as this.)
para :: Functor f => RAlgebra f a > Term f > a
para f = histo (fmap worker >>> f) where
worker (Attr a h) = (In (fmap (worker >>> fst) h), a)
Throughout this series, we can derive unfolds from a corresponding fold by "reversing the arrows"—viz., finding the function dual to the fold in question. And the same holds true for histomorphisms—the dual is very powerful. But, to find the dual of histo
, we must first find the dual of Attr
.
Whereas our Attr
structure held both an a
and a recursive f (Attr f a)
structure, its dual—CoAttr
—holds either an a
value—we'll call that Stop
—or a recursive f (CoAttr f a)
value, which we'll call Continue
. (Put another way, since Attr
was a product type, its dual is a sum type.) The definition follows:
data CoAttr f a
= Stop a
 Continue (f (CoAttr f a))
So why call these Continue
and Stop
? It's simple—returning a Stop
value from our CVcoalgebra means that the unfold should not operate on this level, allowing us to unfold more than one level at a time into the future. By contrast, returning a Continue
value tells the unfold to continue at this level. This is why we call them futumorphisms—our CVcoalgebra allows us to determine the future of the unfold. (The term 'futumorphism' is etymologically dubious, since the 'futu' prefix is Latin and the 'morpho' suffix is Greek, but there are many other examples of such dubious words: 'television', 'automobile', and 'monolingual', to name but a few.)
type CVCoalgebra f a = a > f (CoAttr f a)
Like its predecessor unfolds cata
and para
, the futumorphism will take a coalgebra, a seed value a
, and produce a term f
:
futu :: Functor f => CVCoalgebra f a > a > Term f
We derived the anamorphism and apomorphism by reversing the arrows in the definitions of cata
and para
. The same technique applies here—>>>
becomes <<<
, and In
becomes out
. And as previously, we use a type hole to derive the needed signature of the helper function.
futu :: Functor f => CVCoalgebra f a > a > Term f
futu f = In <<< fmap _worker <<< f
/Users/patrick/src/morphisms/src/Main.hs:28:32: error:
• Found hole: ‘_worker’ with type :: CoAttr f a > Term f
This also makes sense! The worker function we used in histo
was of type Term f > Attr f a
—by reversing the arrows in this worker and changing Attr
to CoAttr
, we've derived the function we need to define futu
. And its definition is straightforward:
futu :: Functor f => CVCoalgebra f a > a > Term f
futu f = In <<< fmap worker <<< h where
worker (Continue a) = futu f a  continue on this level
worker (Stop g) = In (fmap worker g)  omit folding this level,
 delegating to the worker
 to perform any needed
 unfolds later on.
When we encounter a plain Continue
value, we continue recursing into it, perpetuating the unfold operation. When we encounter a Stop
value, we run one more iteration on the top layer of the inprogress fold (transforming its children from Coattr f a
values into Term f
values by recursively invoking worker
), then wrap the whole item up with an In
constructor and return a final value. The product of this nested invocation of worker
is then similarly passed to the In
constructor to wrap it up in a fixpoint, then returned as the final output value of futu
.
What differentiates this from apo
—which, if you recall, used an Either
type to determine whether or not to continue the unfold—is that we can specify, in each field of the functor f, whether we want to continue the unfold or not. apo
gave us a binary switch—either stop the unfold with a Left
or keep going with a Right
. futu
, by contrast, lets us build out as many layers at a time as we desire, giving us the freedom to manually specify the shape of the structure or relegate its shape to future invocations of the unfold.
This is an interesting way to encode unfolds! A CVcoalgebra that always returns a Continue
value will loop infinitely, such as the unfold that generates all natural numbers. This means that we can tell, visually, whether our unfold is infinite or terminating.
"But Patrick," you might say, "this looks like a cellular automaton." And you would be right—CVcoalgebras describe tree automata. And in turn, coalgebras describe finitestate automata, and Rcoalgebras describe stream automata. We'll use this fact to define an example CVcoalgebra, one that grows^{9} random plant life.
Let's start by defining the various parts of a plant.
data Plant a
= Root a  every plant starts here
 Stalk a  and continues upwards
 Fork a a a  but can trifurcate at any moment
 Bloom  eventually terminating in a flower
deriving (Show, Functor, Foldable)
Let's define a few rules for how a plant is generated. (These should, as I mentioned above, remind students of the rules for tree automata.)
1. Plants begin at the ground.
2. Every plant has a maximum height of 10.
3. Plants choose randomly whether to fork, grow, or bloom.
4. Every fork will contain one immediate bloom and two further stems.
Rather than using integers to decide what action to take, which can get obscure very quickly, let's define another sum type, one that determines the next step in the growth of the plant.
data Action
= Flower  stop growing now
 Upwards  grow up with a Stalk
 Branch  grow up with a Fork
Because we need to keep track of the total height and a random number generator to provide randomness, we'll unfold using a data type containing an Int
to track the height and a StdGen
generator from System.Random
.
data Seed = Seed
{ height :: Int
, rng :: StdGen
}
We'll define a function grow
that takes a seed and returns both an randomlychosen action and a new seed containing the subsequent state of the random number generator. We'll generate an action by choosing a random number from 1 to 5: if it's 1 then we'll choose to Flower
, if it's 2 we'll choose to Branch
, and otherwise we'll choose to grow Upwards
. (Feel free to change these values around and see the difference in the generated plants.) The Int
determining the height of the plant is incremented every time grow
is called.
grow :: Seed > (Action, Seed)
grow (Seed height rand) = (choose choice, Seed (height + 1) nextrand)
where (choice, rand') = randomR (1 :: Int, 5) rand
(_, nextrand) = split rand'
choose 1 = Flower
choose 2 = Branch
choose _ = Upwards
And now we'll define a CVcoalgebra, one that takes a Seed
and returns a Plant
containing a CoAttr
value.
sow :: CVCoalgebra Plant Seed
The definition falls out rather quickly. We'll start by growing a new seed, then examining the current height of the plant:
sow seed =
let (action, next) = grow seed
in case (height seed) of
Since we'll start with a height value of 0, we'll begin by generating a root (rule 1). Because we want to immediately continue onwards with the unfold, we pass a Continue
into this Root
, giving it the subsequent seed (so that we get a new RNG value).
0 > Root (Continue next)
Rule 2 means that we must cap the height of the plant at 10. So let's do that:
10 > Bloom
Otherwise, the height is immaterial. We must consult the action
variable to know what to do next.
_ > case action of
If the action is to Flower
, then we again return a Bloom
.
Flower > Bloom
If it's to grow Upwards
, then we return a Stalk
, with a contained Continue
value to continue our fold at the top of that Stalk
:
Upwards > Stalk (Continue next)
And now we handle the Branch
case. Our rules dictate that one of the branches will stop immediately, and the other two will continue, after a given length of Stalk
. So we return a Fork
with one Stop
and two Continues
.
Branch > Fork  grow a stalk then continue the fold
(Stop (Stalk (Continue next)))
 halt immediately
(Stop Bloom)
 again, grow a stalk and continue
(Stop (Stalk (Continue next)))
Note how, even though we specify the construction of a Stalk
in the first and third slots, we allow the fold to Continue
afterwards. This is the power of the futumorphism: we can choose the future of our folds, layer by layer. This is not possible with an anamorphism or apomorphism.
Here's our full sow
function, rewritten slightly to use one case
statement:
sow :: CVCoalgebra Plant Seed
sow seed =
let (action, next) = grow seed
in case (action, height seed) of
(_, 0) > Root (Continue next)
(_, 10) > Bloom
(Flower, _) > Bloom
(Upwards, _) > Stalk (Continue next)
(Branch, _) > Fork (Stop (Stalk (Continue next)))
(Stop Bloom)
(Stop (Stalk (Continue next)))
This is pretty remarkable. We've encoded a complex set of rules, one that involves both nondeterminism and strict layout requirements, into one CVcoalgebra, and it took just eleven lines of code. No mutable state is involved, no manual accumulation is required—the entire representation of this automaton can be reduced to one pure function.
Now, in our main
function, we can grab an RNG from the global state, and call futu
to generate a Term Plant
.
main :: IO ()
main = do
rnd < newStdGen
let ourPlant :: Term Plant
ourPlant = futu sow (Seed 0 rnd)
Using a rendering function (which I have omitted for brevity's sake, though you can be assured that it is implemented using cata
rather than explicit recursion), we can draw a picture of the plant we've just generated, with little flowers.
⚘
 ⚘ ⚘ ⚘
⚘  
└─┘  
   ⚘
 ⚘   
└─────┘  ⚘ 
 └──────┘
 ⚘ 
└───────────────┘

_
Admittedly, the vaguaries of code page 437 leave us with a somewhat unaesthetic result—but a nicer representation of Plant
, perhaps using gloss or Rasterific, is left as an exercise for the reader.
One final detail: just as we can use an apomorphism to express an anamorphism, we can express anamorphisms and apomorphisms with futumorphisms:
ana :: (Functor f) => Coalgebra f a > a > Term f
ana f = futu (fmap Continue <<< f)
apo :: Functor f => RCoalgebra f a > a > Term f
apo f = futu (fmap (either termToCoattr Continue) <<< f)
where termToCoattr = Stop <<< fmap termToCoattr <<< out
Now we know what histomorphisms and futumorphisms are. Histomorphisms are folds that allow us to query any previous result we've computed, and futumorphisms are unfolds that allow us to determine the future course of the unfold, multiple levels at a time. But, as is so often the case with recursion schemes, these definitions touch on something deeper and more fundamental.
Here's the kicker: our above CoAttr
definition is equivalent to the Free
monad, and Attr
(being dual to CoAttr
) is the Cofree
comonad.
We usually represent Free
, aka CoAttr
, as two constructors, one for pure values and one for effectful, impure values:
data Free f a
= Pure a
 Impure (f (Free f a))
And we usually represent the cofree comonad with an infix constructor, since the cofree comonad is at its heart a glorified tuple:
data Cofree f a = a :< (f (Cofree f a))
The various packages in the Haskell ecosystem implement cata
and para
in much the same way, but the same is not true of histo
and futu
. Edward Kmett's recursionschemes package uses these definitions of Free
and Cofree
(from the free package). fixplate
uses a different definition of Attr
: rather than being a data type in and of itself, it is defined as a Term
over a moregeneral Ann
type. compdata
's is slightly more complicated, as it leverages other typeclasses compdata
provides to define attributes on nodes, but is at its heart the same thing. Each is equivalent.
The free monad, and its cofree comonad dual, lie at the heart of some of the most fascinating constructions in functional programming. I have neither the space nor the qualifications to provide a meaningful explanation of them, but I can enthusiastically recommend Gabriel Gonzales's blog post on free monads, Dan Piponi's post on the cofree comonad, and (of course) Oleg Kiselyov's groundbreaking work on the free and freer monads. But I think the fact that, as we explore as fundamental a construct as recursion, we encounter another similarly fundamental concept of the free monad, provide an argument for the beauty and unity of the categorytheoretical approach to functional programming that is far more compelling than any I could ever make myself.
I'd like to thank Rob Rix, who was essential to this work's completion, and Colin Barrett, who has been an invaluable resource on the many occasions when I find myself stuck. I'd also like to thank Manuel Chakaravarty, who has done this entire series a great favor in checking it for accuracy, and Jeanine Adkisson, who found some outrageous bugs in the provided futumorphism. Greg Pfiel, Scott Vokes, and Josh Bohde also provided valuable feedback on drafts of this post. Next time, we'll explore one of the most compelling reasons to use recursion schemes—the laws that they follow—and after that, we'll discuss the constructs derived from combining unfolds with folds: the hylomorphism and the chronomorphism.
Bob Harper, in Practical Foundations for Programming Languages, refers to In
and out
as "rolling" and "unrolling" operations. This is a useful visual metaphor: the progression f (f (Term f)) > f (Term f) > Term f
indeed looks like a flat surface being rolled up, and its opposite Term f > f (Term f) > f (f (Term f))
looks like the process of unrolling.↩
Rob Rix points out that, though catamorphisms are often described as "bottomup", this term is ambiguous: catamorphisms' recursion occurs topdown, but the folded value is constructed bottomup. I had never noticed this ambiguity before. (The words of Carroll come to mind: "'When I use a word,' Humpty Dumpty said, in rather a scornful tone, 'it means just what I choose it to mean — neither more nor less.'")↩
Unfortunately, in this context I think "classic" can be read as "hackneyed and unhelpful". I dislike using fib()
to teach recursion schemes, as the resulting implementations are both more complicated than a straightforward implementation and in no way indicative of the power that recursion schemes bring to the table. Throughout this series, I've done my damnedest to pick interesting, beautiful examples, lest the reader end up with the gravely mistaken takeaway that recursion schemes aren't useful for any realworld purpose.↩
A word with a rich pedigree—most directly from the Greek 'ἱστορία', meaning a narration of what has been learned, which in turn descended from 'ἱστορέω', to learn through research, and in turn from 'ἵστωρ', meaning the one who knows or the expert— a term commensurate with the first histories being passed from person to person orally. And the Greek root 'ἱστο', according to the OED, can be translated as 'web': a suitable metaphor for the structural web of values that the Attr
type generates and preserves.↩
A feature taken wholesale, we must note, from dependentlytyped languages like Agda and Idris.↩
Natch.↩
Keeneyed readers will note that this data type is isomorphic to the Maybe
type provided by the Prelude. We could've just used that, but I wanted to make the numeric nature of this structure as clear as possible.↩
There is no reason why we couldn't do this—I just chose to omit it for the sake of brevity.↩
which brings an amusing literalism to the term 'seed value'↩
As always, if you'd like to follow along with the code, it is preserved here.
In the past two posts, we defined a datatype Term
that represents the fixedpoint of a functor f
, with an In
constructor that ties an f (Term f)
into a Term f
, and an out
deconstructor that unties a Term f
into an f (Term f)
:
newtype Term f = In { out :: f (Term f) }
Using fmap
, and the property that fmap
is the identity function over a Functor
with no children, we can define a bottomUp
function that applies a typepreserving transformation to any Term f
:
bottomUp :: Functor a => (Term a > Term a) > Term a > Term a
bottomUp fn =
out  1) unpack
>>> fmap (bottomUp fn)  2) recurse
>>> In  3) repack
>>> fn  4) apply
And, when we omit the repacking stage in the above definition, we yield cata
, a generalized fold operator that allows us to collapse a given Term f
into an accumulated value a
, using an Algebra
that reunites an f a
into an a
:
type Algebra f a = f a > a
cata :: (Functor f) => Algebra f a > Term f > a
cata fn =
out  1) unpack
>>> fmap (cata fn)  2) recurse
>>> fn  3) apply
Catamorphisms are simple and elegant, but in many realworld usecases they are insufficient. For example, though the function we pass to cata
allows us to view the data we're transformating, it loses information about the original structure: in the case of prettyprinting, we only have access to the currentlyprinted tree (the f Doc
for any fix): any information about the structure of the original Term Expr
is lost, as it has already been prettyprinted.
This would be a problem if you wanted to print, say, zeroargument functions in a different manner than other functions: you'd have to use ==
to examine the Doc
type, then dispatch on the result to implement this different behavior. And your Doc
type may omit an Eq
instance, so ==
may not even be possible! (Besides, looking at the prettyprinted results to infer their inputs is clumsy at best and often inaccurate in the presence of hyphenation or spacing.)
It would be ideal if our Algebra
could, when examining an Expr
node, have access both to its prettyprinted representation and its original representation as a Term
—an Algebra
that has access to the original, untransformed datum, represented as its fixedpoint Term
. That is to say, whereas Algebra
is a function from a container f
of a
, we want a function from container that holds both a Term f
and its corresponding a
. Rather than using a function with two arguments, we'll bundle these two arguments together in a tuple (for reasons that will become clear later).
f (Term f, a) > a
Algebras that carry this extra information are known as Ralgebras.
type RAlgebra f a = f (Term f, a) > a
There is another type of morphism that allows you to traverse a structure with an Ralgebra: the paramorphism. As with previous examples, I'm going to explicate the etymology in the hope that it slightly illuminates a complicated concept: the para in paramorphism is the same as in parallel—from the Greek παρά, meaning "beside", "next to", or "alongside"^{1}. A paramorphism is like a catamorphism except that it can view the original structure beside the structure that is being transformed.
So, let's implement paramorphisms. They'll look like the catamorphism we already discussed. But instead of just directly recursing into the structure with fmap para
, we have to recurse with a function that returns a tuple, one that contains both the Term
we're recursing into and the result of recursively applying para
. We do this here with a helper function called fanout
, which takes a Term f
and returns both items in which we are interested: the Term
itself, and the result of recursively applying para
.
para :: (Functor f) => RAlgebra f a > Term f > a
para rAlg = out >>> fmap fanout >>> rAlg
where fanout :: Term f > (Term f, a)
fanout t = (t, para rAlg t)
And we're done! This is the classical definition of a paramorphism. With it, we can have our cake and eat it too: we can intelligently fold over a data structure without losing any information about the original representation of said structure.
And Haskell comes with a function that makes expressing the above fanout
function even more elegant than it already is. The &&&
combinator, provided in the Control.Arrow
module, takes two functions^{2} foo
and bar
and returns another function that, when given a value a
, returns a tuple consisting of (foo a, bar a)
. Simply put, it combines the output of two functions.
Our fanout
function, when provided with a Term f
, needs to do two things: preserve its input in the first element of the tuple, and recurse with para
into its input, providing the result of doing so in the second element. We can use &&&
to express this concisely: given a Term f
, we preserve the element with the identity function id
, and apply para
recursively to the argument. Let's express this as such:
fanout = id &&& para f
Now we can express para
with our new, beautiful fanout
function:
para' :: Functor f => RAlgebra f a > Term f > a
para' f = out >>> fmap (id &&& para' f) >>> f
The type signatures indicate that both these formulations of paramorphisms are equivalent: which one you choose is entirely up to which one you find more aesthetically pleasing.
For extra credit: rather than using a function that takes a container of tuples (which can strike the eye as somewhat ugly), we can use one that takes two arguments, both the Term f
and the container f a
. (We can convert between this representation and RAlgebra
trivially).
type RAlgebra' f a = Term f > f a > a
Balazs Komuves refers to this formulation as "slightly less natural" in his Fixplate library. The implementation is indeed less pleasing, as it cannot easily be expressed in a pointfree fashion, but it has a nice property that we'll explore below.
 The & function is reverse function application,
 just like the $ operator, but with its arguments flipped.
para'' :: Functor f => RAlgebra' f a > Term f > a
para'' alg t = out t & fmap (para'' alg) & alg t
And just as we were able to represent bottomUp
in terms of cata
, we can express cata
in terms of para'
—after all, a catamorphism is merely a paramorphism that ignores the provided Term
. And Haskell provides the const
function (aka the Kcombinator) for just these situations where we want to ignore an argument to a function:
cata' :: Functor f => Algebra f a > Term f > a
cata' f = para'' (const f)
Beautiful, no? This is one of the really appealing things about recursion schemes: as we explore more and more powerful constructs, we see how the lesspowerful constructs can be implemented straightforwardly in terms of more general ones.
The identity function id
, by definition, returns its argument unchanged: id(x)
can be replaced with x
in every case. Let's imagine a prettyprinter that, for some reason^{3}, performs this optimization step on its output.
To do this with a simple catamorphism, we'd need to check every functioncall's prettyprinted name to determine whether it is id
, then return the argument unchanged—and, as I mentioned above, our prettyprinted Doc
representation shouldn't even support an equality operation, so examining it is a nogo. However, we can do this easily with a paramorphism. In order to avoid having to write a bunch of tuples, I'm going to use the second representation of Ralgebras above (the ternary function), and I'm going to use the Expr
syntax tree defined in previous installments.
fastPretty :: RAlgebra' Expr Doc
 All our cases, aside from the `Call` nodes in which
 we are interested, are the same as in the prettyprinting
 catamorphism in the previous installment. We just ignore
 the first `Term` argument because it doesn't have anything we need
 to look at.
fastPretty _ (Literal i) = P.int i
fastPretty _ (Ident s) = P.text s
 Here's where it gets interesting. We're going to look
 at the first argument to determine whether this is a
 `Call` node with the function name (an `Ident`) named `id`.
 If so, we'll just return the only argument provided.
fastPretty (In Call { func = "id" })
Call {args = [theArg]} = theArg
fastPretty _ (Call f as) = f <> P.parens (P.cat (P.punctuate ", " as))
 The other cases are the same as `prettyPrint` in the last installment.
During complicated tree transformations, the context of the structure you're transforming will eventually come into play. Catamorphisms don't let you examine this context, but paramorphisms do.
In the previous post, we defined ana
, the anamorphism, a generalized unfold operating on any given data type to generate a Term f
. While unfolds are a little more abstruse and less common than folds, it's worth walking through their construction, if only to observe the generality achieved from reversing the arrows in a given morphism.
We expressed ana
as the dual to cata
, replacing instances of out
with In
, and replacing lefttoright function composition with the righttoleft equivalent, <<<
(more commonly expressed with Haskell's .
function)—in short, reversing the arrows of the definition.
cata f = out >>> fmap (cata f) >>> f
ana f = In <<< fmap (ana f) <<< f
And we defined the function argument that ana
takes as a Coalgebra
, seeing as how it is dual to the Algebra
we already defined:
type Coalgebra f a = a > f a
ana f :: (Functor f) => Coalgebra f a > a > Term f
It stands to reason that we can define the dual of a paramorphism—a coparamorphism. But, as always, we have a better name for this: the dual of a paramorphism is an apomorphism. Just as the ana prefix is the opposite of the cata prefix, so the para prefix is the opposite of the apo prefix. In this case, apo comes from the Greek ἀπο, meaning "away from" or "separate", as in "apogee" (the moon being away from the earth) or "apostasy" (someone turning away from their beliefs).
So, let's start by defining the categorical dual of the Ralgebra. We've reversed the arrows in every case, so the following definition should be correct, right?
type Nope = a > f (Term f, a)
Wrong! We have to apply the dual to every construct in the definition of RAlgebra
. We need to reverse the direction of the function, yes, but we also need to reverse the tuple associated with the above definition. So what's the dual of a tuple?
Well, let's consider what a tuple is for. Given two arguments big
and pac
, a tuple bundles both of them together as (big, pac)
. That makes sense, yes, but what can we do with both of these arguments that fits the notion of the "opposite" of holding both? Well, we can hold one or the other. And Haskell provides a concept to hold either a big
or a pac
: namely, Either
. So, given that an Algebra f a
holds a Term f
and an a
, we can express the dual of an Ralgebra using an Either
:
type RCoalgebra f a = a > f (Either (Term f) a)
But what does this mean when we're using apomorphisms in practice? Well, it allows us to separate the flow of computation during our unfolds. If our Rcoalgebra returns a Left
value in which is contained a Term
, the apomorphism will terminate and return the provided value. If it returns a Right
value containing an f a
, the unfold will continue onwards. This is cool! The ability to terminate during a corecursive iteration depending on the argument is a very useful property—and we need no imperative constructs such as break
or exceptions.
So, just as we expressed ana
by reversing the arrows of cata
, we can express apo
by reversing the arrows of para
:
para :: Functor f => RAlgebra' f a > Term f > a
para f = out >>> fmap fanout >>> f where fanout = id &&& para f
apo f :: Functor f => RCoalgebra f a > a > Term f
apo f = In <<< fmap fanin <<< f where fanin = ???
It may not be immediately obvious how to implement fanin
. But, when you reverse the arrows of the fanout
definition above (I have omitted said reversal for brevity's sake), you'll discover that you yield a function that takes an Either (Term f) a
and returns a Term f
.
fanin :: Either (Term f) a > Term f
As such, our function will handle this either by applying id
in the case of a Left
value (as getting a Term
means that we can just return the Term
) and recursing with apo
in the case of a plain old a
value, out of which we ultimately yield a Term
, thanks to the ultimate signature of apo
. And Haskell's builtin either
function, which takes two functions and an Either and returns a result of applying the first to a Left
case or the second to the Right
case, allows us to express this fanin
function beautifully. id
does nothing the value contained inside a Left
, returning just a Term f
, and apo f
continues the unfold operation when provided a Right
:
apo :: Functor f => RCoalgebra f a > a > Term f
apo f = In <<< fmap fanin <<< f where fanin = either id (apo f)
Similarly, we can rewrite fanin
with 
, the dual of the &&&
function above. (The operators here are a useful visual mnemonic: &&&
uses both the functions it provides, where as 
uses one or the other).
apo :: Functor f => RCoalgebra f a > a > Term f
apo f = In <<< fmap (id  apo f) <<< f
If you made it this far, you've got some serious chops, and I salute you. Next time, we'll look at futumorphisms and histomorphisms, and uncover some seriously powerful constructs (and some seriously dubious etymologies).
I am indebted to Rob Rix, Colin Barrett, and Manuel Chakravarty for their input and suggestions regarding this post.
In part four, we explore histomorphisms and futumorphisms.
Modern English tends to use "para" as a prefix meaning "pseudo" or "abnormal" (as in "parapsychology" or "paresthesia")—this is an extension of the "alongside" meaning, implying that abnormal things appear alongside normal things. Be sure not to confuse these two meanings—there's nothing abnormal or secondclass about paramorphisms.↩
Technically, two Categories
, but if you use instances of Category
beyond (>)
then you are way ahead of me.↩
You'd have to be a complete lunatic to put this optimization step in your prettyprinter—you'd either perform this as an optimization over the original code or during a conversion to a separate intermediate representation—but I'm going to stick with this incredibly contrived example, because the Doc
type makes it very clear, when operating on Expr
types, when and where the prettyprinting step is happening.↩
On the last episode of this veryinfrequentlyupdated analysis of Programming with Bananas, Lenses, Envelopes, and Barbed Wire, we took a look at how to represent and traverse nested structures. This time, we’ll start getting into the meat of the paper^{1}. We’ll define two simple recursion schemes, explore
]]>On the last episode of this veryinfrequentlyupdated analysis of Programming with Bananas, Lenses, Envelopes, and Barbed Wire, we took a look at how to represent and traverse nested structures. This time, we’ll start getting into the meat of the paper^{1}. We’ll define two simple recursion schemes, explore how they relate to each other, and I’ll offer up some examples (both realworld and contrived) of situations that admit the use of recursion schemes.
We’ll start by defining a simple syntax tree, as we did last time. If you'd like to run the code, an implementation and test suite can be found here.
{# LANGUAGE DeriveFunctor #}
data Expr a
= Literal { intVal :: Int }
 Ident { name :: String }
 Index { target :: a, idx :: a }
 Unary { op :: String, target :: a }
 Binary { lhs :: a, op :: String, rhs :: a }
 Call { func :: a, args :: [a] }
 Paren { target :: a }
deriving (Show, Eq, Functor)
We define two root constructors to represent the leaf nodes of the tree (Literal
and Ident
, wrapping Int
and String
values respectively) and five other constructors. Every position in which subexpressions may appear – the lefthand and righthand sides to a binary operation, the target of a unary operation, the function and arguments in a function invocation – is represented by a value of type a
, in terms of which Expr is defined^{2}.
We use the DeriveFunctor
GHC extension to provide a definition of the Functor
typeclass for Expr
, which provides us with an fmap
function. fmap f
, applied to a given Expr
, applies f
to each subexpression a
, and is the identity function when passed a Literal
or Ident
value, as they contain no subexpressions.
At this point, we can represent expressions with no subexpressions with the type Expr ()
; for expressions at most one level of subexpressions, we can use Expr (Expr ())
; for two, Expr (Expr (Expr ())
, and so on and so forth. What we want is to represent an arbitrarilynested Expr
– in essence, an Expr (Expr (Expr (Expr ...)))
– but, since Haskell doesn’t support infinite types, we have to resort to a leastfixedpoint combinator. We call this combinator Term
, with an In
constructor and an out
accessor function.
newtype Term f = In { out :: f (Term f) }
Now we represent arbitrarilynestable Expr
s with values of type Term Expr
, obtained by applying the In
constructor with a constructor from Expr
^{3}:
ten, add, call :: Term Expr
ten = In (Literal { intVal = 10 })
add = In (Ident { name = "add" })
call = In (Call { func = add, args = [ten, ten]})  add(10, 10)
Using the >>>
operator for lefttoright function composition, we expressed a generalized bottomup traversal capable of operating on any data type that implements the Functor
typeclass.
bottomUp :: Functor a => (Term a > Term a) > Term a > Term a
bottomUp fn =
out  1) unpack a `Term a` into an `a (Term a)`
>>> fmap (bottomUp fn)  2) recurse, with fn, into the subterms
>>> In  3) repack the `a (Term a)` into a `Term a`
>>> fn  4) finally, apply fn to the packed `Term a`
While this is a pleasing and concise representation of bottomup transformations, it’s not as powerful as it could be: specifically, fn
is limited to taking and returning a value of type Term f
. We could not use bottomUp
to count the total number of subexpressions in a tree (going from Expr
s to Int
s), nor could we transform this tree to a DOM representation (Node
), nor could we render a term into a prettyprinted representation (Doc
).
This is a direct result of the third step above: after recursing into the data structure with fn
in step #2, we stuff it into a Term
using the In
constructor in step #3 before passing it to fn
. This forces fn
to both take and return a Term
. What if we wrote a version of bottomUp
that omitted that particular call to In
? Would it typecheck?
It does:
mystery fn =
out  1) unpack the Term
>>> fmap (mystery fn)  2) recursively apply `fn`
>>> fn  3) apply `fn`
Loading this function in ghci
and querying its type yields the following result:
λ> :t mystery
mystery :: Functor f => (f a > a) > Term f > a
Cool. How does this work?
Let’s take a closer look at that first parameter:
Functor f => (f a > a)
This is a function type, taking as input a container^{4} f
of values of type a
and returning a bare value of type a
. If we wanted to write a countNodes
function that counts (as an Int
) the number of subexpressions within a given Term Expr
, f
would be equal to Expr
(which has a Functor
instance), and a
would be Int
.
countNodes :: Expr Int > Int
The crucial element of understanding this function is its first parameter, here Expr Int
. It captures an Expr
in the process of transformation – because subexpressions (the lhs
and rhs
field of Binary
, the func
and args
fields of Call
) were represented as values of the parameterized type a
, we can capture the bottomup nature of transformation in the type itself. This means that each case for countNodes
is easy to express: merely add 1 to the sum of all the contained subexpressions.
countNodes (Unary _ arg) = arg + 1
countNodes (Binary left _ right) = left + right + 1
countNodes (Call fn args) = fn + sum args + 1
countNodes (Index it idx) = it + idx + 1
countNodes (Paren arg) = arg + 1
And our base case for this transformation falls out easily: Literal
and Ident
have no subexpressions, so they just return 1:
countNodes (Literal _) = 1
countNodes (Ident _) = 1
Applying mystery countNodes
to our example add(10, 10)
datum should yield 4. Does it?
λ> mystery countNodes call
4
Dope.
I had a major mental block in understanding when, in fact, the countNodes
function was actually called, and how mystery
managed to recurse as deeply as possible into the passed structure. The key lies in the invocation of fmap
within mystery
:
fmap (mystery fn)
Because fmap f
applies f
to each subexpression within an Expr
, mystery
starts out by recursing as deeply as possible into the Term
it is passed, since it calls itself recursively with fmap
. It’s almost magical how mystery
“knows” how to stop recursing – but it lies in the definition of fmap
. fmap mystery
is the identity function over Literal
and Ident
values, as they do not contain any subexpressions. At this point, mystery
stops recursing, applies the function f
, and returns into its previous invocations. As the stack unwinds, fn
gets applied to the nexthighest node in the tree, then the next, then the next, until all the original Expr
values are gone and we yield only an a
. It all comes down to the capabilities of fmap
– from a straightforward purpose and declaration emerges a deep and subtle way to fold over a structure.
Indeed, functions of type f a > a
are so ubiquitous that we refer to them by their own name:
type Algebra f a = f a > a
When you see a value of type Algebra f a
, know that it’s a function from a container f
to a collapsed value a
. We could rewrite the type signature of the above countNodes
function to use it:
countNodes :: Algebra Expr Int
The use of the term “algebra” to in this context may seen a bit discomfiting. Most people use the term “algebra” to describe manipulating numerical expressions (as well as the associated drudgery of childhood math courses). Why are we overloading this term to represent a class of functions?
The etymology of “algebra” can shed some light on this—it stems from the Arabic root جبر, jabr, which means “restoration” or “reunion”. An algebra is a function that reunites a container of a
’s—an f a
—into a single accumulated a
value.
So, now that we have a grasp on what an algebra is and why we’re calling it that, let’s rewrite the type signature of mystery
using the Algebra
type synonym.
mystery :: (Functor f) => Algebra f a > Term f > a
This mystery
function is known as a catamorphism, usually given the name cata
:
cata :: (Functor f) => Algebra f a > Term f > a
cata f = out >>> fmap (cata f) >>> f
Again, though the term “catamorphism” may seem needlessly arcane, its etymology both simplifies and clarifies its purpose: the “cata” in “catamorphism” is the same “cata” in “catastrophe”, “catabolism”, and “catalyst”—from the Greek κατα, meaning “downwards”, “into”, or “collapse”. Just as a catastophe is an unlucky event that tears things down, and catabolism is the collapse of muscle fibers, a catamorphism uses an algebra (a reunion) to collapse a container of values into a single value.
If you’re reminded of the fold
operation on lists, you’re on the right track: a list fold (specifically foldr
^{5}) is merely a catamorphism limited to operate on lists (the []
type, the archetypal Functor
). That’s the essence of catamorphisms: they’re generalized fold operations, applicable to any functor—not just lists—all without having to write a line of boilerplate traversals and without sacrificing a mote of type safety.
Another excellent example of catamorphisms is prettyprinters for trees. If the representation you require of a tree type can be expressed in a purely bottomup manner – that is to say, it requires no information about the original structure of the tree being transformed – you can express prettyprinting functions in a truly wonderfully concise manner. (Spoiler alert: later on, we’ll figure out how to do this even when you need access to the original structure.) We can describe a prettyprinting algebra from Expr
to Doc
, an abstract document type provided by the Text.PrettyPrint module.
import Text.PrettyPrint (Doc)
import qualified Text.PrettyPrint as P
prettyPrint :: Algebra Expr Doc
Again, expanding the type synonym yields us a function type, going from Expr
s with values of type Doc
as their subexpressions, out to a final Doc
value. These values represent the leaves of the nodes that have already been prettyprinted.
prettyPrint :: Expr Doc > Doc
Our base cases are straightforward:
prettyPrint (Literal i) = P.int i
prettyPrint (Ident s) = P.text s
And our inductivelydefined cases are beautifully clutterfree:
 f(a,b...)
prettyPrint (Call f as) = f <> P.parens (P.cat (P.punctuate ", " as))
 a[b]
prettyPrint (Index it idx) = it <> P.brackets idx
 op x
prettyPrint (Unary op it) = P.text op <> it
 lhs op rhs
prettyPrint (Binary l op r) = l <> P.text op <> r
 (op)
prettyPrint (Paren exp) = P.parens exp
Applying prettyPrint
to our example call
datum should yield a nice representation:
λ> cata prettyPrint call
add(10,10)
One final detail: we can represent our bottomUp
function in terms of cata
. Indeed, bottomUp f
is just cata f
, with the additional step of stuffing the accumulator value into a Term
with In
before handing it off to f
:
bottomUp f = cata (In >>> f)
In the last post, we defined a corresponding topDown
method, the opposite of bottomUp
. Furthermore, we did so by “reversing the arrows” in bottomUp
: we changed the lefttoright composition operator >>>
to its flipped <<<
(which is equivalent to the .
operator), and we flipped around all invocations of out
to In
and In
to out.
bottomUp f = out >>> fmap (bottomUp f) >>> In >>> f
topDown f = In <<< fmap (topDown f) <<< out <<< f
By applying this “reversing the arrows” trick, we managed to express the duality between bottomup and topdown traversals. So what happens if we do the same to our definition of cata
? Does it typecheck?
cata f = out >>> fmap (cata f) >>> f
 reverse the arrows: >>> becomes <<<. and out becomes In
what f = In <<< fmap (what f) <<< f
You’re damn right it does. What’s its type signature?
what :: (Functor f) => (a > f a) > a > Term f
Just as when we yielded a topdown traversal by reversing a bottomup one, we yield an unfold when we reverse the arrows in our generalized fold function. Again, let’s take a look at the type of the first parameter, the unfolding function:
Functor f => (a > f a)
Similar to our Algebra
type a > f a
, right? Exactly the same, except the direction of the function arrow has reversed: whereas algebras are mappings from f a
to a
, this new function type maps a
s to f a
s. We call this function type a coalgebra.
type Coalgebra f a = a > f a
We call the what
an anamorphism – the “ana” prefix, meaning “building”, is the opposite of “cata”. Just like cata
generalized fold
from lists to any Functor
, ana
generalizes unfold
to any Functor
.
ana :: (Functor f) => Coalgebra f a > a > Term f
ana f = In <<< fmap (ana f) <<< f
If we thought about algebras as reunions, we can think about coalgebras as disassembly or dispersion. We’re taking one established a
value and stuffing it inside a context f
. If that f
is Maybe
, then we’re allowing for the possibility that no value will be present; if that f
is []
, the list monad, it means we’re allowing there to be zero or more results. (Extremely alert readers of the Typeclassopedia will notice that Algebra
and Coalgebra
are extraordinarily similar to the Pointed
and Copointed
typeclasses. Bully for you if you noticed that.)
Thanks very much for Rob Rix for encouraging me to publish this.
On a personal note, I am deeply grateful for all the comments I’ve received thus far, both complimentary and constructive.
In part 3, we explore the limits of catamorphisms, and address these limits with paramorphisms and apomorphisms.
By “meat of the paper”, I mean “the first two pages”. Have patience.↩
In other words, Expr
is an inductivelydefined data type of kind * > *
.↩
Packages like compdata provide Template Haskell magic to avoid the syntactic clutter associated with applying the In
constructor everywhere: given our above definition of Expr
, we could use the smartConstructors
splice to generate iLiteral
, iIdent
, iUnary
, etc. functions, each of which are straightforward compositions of the constructors of Expr
with the In
fixedpoint operator.↩
The purists in the audience are no doubt cringing at my use of “container” to describe the capabilities of a Functor
. As the Typeclassopedia points out, Functor
s are broader than just containing values: the best phrase for a Functor f => f a
is “a value a
within some computational context f
”. That doesn’t exactly roll off the tongue, though, so I will be using ‘container’ throughout. Sorry, purists. (But I already lost you with my handwavy treatment of leastfixedpoints and codata last time.)↩
Digression: Some of you may be wondering “if the catamorphism applied to lists is equivalent to foldr
, what’s the analogous construct for foldl
?” This is somewhat of a trick question, as foldl
can be expressed in terms of foldr
, as Oleg demonstrates.↩
Because nested structures appear in almost every problem domain and programming environment, from databases to 3D graphics to filesystems, the act of iterating through these structures is common, so common that most programmers barely notice when they’re doing it. As such, generalizing the act of recursive traversals provides immediate realworld benefits: our new generalized traversal can replace a host of typespecific traversal functions. In addition, by decoupling how a function recurses over data from what the function actually does, we reduce cognitive overhead and can focus entirely on the core behavior of our recursive functions. No matter the structures in question—lists, directory hierarchies, control flow graphs, database records—recursion schemes bring us an orderly and predictable way to traverse them. In addition, recursion schemes aren’t a product of any one programming language or environment—you can express recursion schemes in any language with firstclass functions. Clojure, for example, uses them to power its clojure.walk API for generically traversing sexpressions and maps.
Meijer et. al go so far as to condemn functional programming without recursion schemes as morally equivalent to imperative programming with goto
. While comparisons to Djikstra’s infamous letter to the ACM are often inane, the analogy is apt: just as using while
and for
loops rather than goto
brings structure and harmony to imperative control flow, the use of recursion schemes over handwritten brings similar structure to recursive computations. This insight is so important that I’ll repeat it: recursion schemes are just as essential to idiomatic functional programming as for
and while
are to idiomatic imperative programming.
I’ve chosen to express the ideas in Bananas, Lenses, Envelopes and Barbed Wire in Haskell, though the paper was written years before Haskell came to prominence^{1}. If you don’t know Haskell very well, don’t panic: you don’t need to be a Haskell whiz to understand the ideas presented here. I assume only a basic familiarity with Haskell syntax and the use of algebraic data types. I’m going to rely on a few idioms to better illustrate the concepts underlying recursion schemes—when I do, I will explain what happens behind the scenes. If you’re wholly unfamiliar with Haskell, you may want to dip into the first few chapters of Learn You a Haskell.
I’ll start with the simplest way to represent a welltyped syntax tree, then show how that simplicity makes it difficult to write a function that generically traverses and modifies trees. I’ll then redefine our syntax tree so as to take advantage of existing Haskell idioms and the expressive power of parameterized data types. Finally, I’ll show how recursion schemes emerge naturally when we express stepbystep descriptions of recursion patterns with common Haskell idioms.
If you'd like to follow along with the code, you can find it in this GitHub repository. An HUnit test suite is included to verify the provided examples.
Let’s take a look at the simplest way to represent a syntax tree in Haskell: an ordinary algebraic datatype.
data Lit
= StrLit String
 IntLit Int
 Ident String
deriving (Show, Eq)
data Expr
= Index Expr Expr
 Call Expr [Expr]
 Unary String Expr
 Binary Expr String Expr
 Paren Expr
 Literal Lit
deriving (Show, Eq)
data Stmt
= Break
 Continue
 Empty
 IfElse Expr [Stmt] [Stmt]
 Return (Maybe Expr)
 While Expr [Stmt]
 Expression Expr
deriving (Show, Eq)
This is a perfectly adequate syntax tree: it’s simple, straightforward, and works nicely with parsing libraries such as attoparsec or Peggy. Yet writing a function that operates on a Expr
node and all its subexpressions is a tedious exercise indeed: here’s an example that flattens an Expr
, recursively removing all Paren
nodes:
 this would turn the expression
 (((anArray[(10)])))
 into
 anArray[10]
flatten :: Expr > Expr
 base case: do nothing to literals
flatten (Literal i) = Literal i
 this is the important case: we shed the Paren constructor and just
 apply `flatten` to its contents
flatten (Paren e) = flatten e
 all the other cases preserve their constructors and just apply
 the flatten function to their children that are of type `Expr`.
flatten (Index e i) = Index (flatten e) (flatten i)
flatten (Call e args) = Call (flatten e) (map flatten args)
flatten (Unary op arg) = Unary op (flatten arg)
flatten (Binary l op r) = Binary (flatten l) op (flatten r)
This code is oppressive, ugly, and unmaintainable. Four out of this function’s six lines are dedicated to the simple yet tedious task of ensuring that flatten
properly recurses into its argument’s subexpressions—not only is this boring to write, but any future changes (such as added constructors or fields) to Expr
will force us to rewrite it. (I’ll refer to recursion written in this style as explicit recursion, in contrast with the implicit recursion provided by recursion schemes.) In addition, it’s extremely easy to make mistakes in this definition—the syntatic noise that the primitive recursion introduces renders it hard to spot a missing recursive invocation of flatten
, yet even one such omission introduces a critical bug.
We can, however, bring some sanity to this madness by writing a function apply
that, given a function f
operating on Expr
s, applies f
to each subexpression of a given Expr
:
applyExpr :: (Expr > Expr) > Expr > Expr
 base case: applyExpr is the identity function on constants
applyExpr f (Literal i) = Literal i
 recursive cases: apply f to each subexpression
applyExpr f (Paren p) = Paren (f p)
applyExpr f (Index e i) = Index (f e) (f i)
applyExpr f (Call e args) = Call (f e) (map f args)
applyExpr f (Unary op arg) = Unary op (f arg)
applyExpr f (Binary l op r) = Binary (f l) op (f r)
By separating out the act of recursing over subexpressions, we can reduce our sixline definition of flatten to two lines. In the body of flatten
, we need only specify that Paren
nodes be treated differently than other nodes, relying on the applyExpr
function to take care of recursion for us:
flatten (Paren e) = flatten e
flatten x = applyExpr flatten x
This function just got far, far easier to write and maintain. The apply
function is now responsible for both the base case and the simple recursive case of flattening an expression: all we have to do is define the interesting case, i.e. its handling of Paren
nodes. Awesome.
But let’s not get ahead of ourselves. We haven’t really prevented any boilerplate or eliminated room for bugs here: applyExpr
just contains and isolates the boilerplate, and we’d need to write a new apply
function for each and every new type we define. A sufficiently smart compiler could write them for us. And GHC, being a very smart compiler, can. First, though, we’ll have to make this Expr
data type a little bit more general.
data Expr a
= Index a a
 Call a [a]
 Unary String a
 Binary a String a
 Paren a
 Literal Lit
deriving (Show, Eq)
This new definition of Expr
is identical to our previous one, except here we’ve added a type variable a
and replaced all recursive occurrences of the Expr
type with it. Put another way, we have parameterized this type in terms of its subexpressions. As such, we have to change our definition of applyExpr
: the function we apply to each subexpression can no longer be of type Expr > Expr
, but must become a > a
: indeed, we can make it a > b
, letting the function change the type of an Expr
’s subexpressions if necessary.
apply :: (a > b) > Expr a > Expr b
The sharpeyed among you will notice how similar this function is to the builtin map
function over lists:
 `map` takes a function (a > b) and makes it operate on lists containing 'a's
map :: (a > b) > [a] > [b]
This is not a coincidence: in fact, the apply
function is exactly analogous to map
for lists—you can think about both functions as mapping or promoting a function f
so as to operate on a larger datatype, whether that’s an Expr
type or a list ([]
) type. This pattern of mapping is so common that its generalized version is a central Haskell concept: the typeclass Functor
represents all the types that provide a map
like function, called fmap
^{2}:
class Functor f where
fmap :: Functor f => (a > b) > f a > f b
Countless datatypes—lists, trees, optional (Maybe
) values, IO actions, even functions themselves—implement the Functor
typeclass. Indeed, it’s so common, and implementing fmap
is usually so straightforward, that GHC provides a builtin mechanism to write the definition of fmap
for you: we can just add Functor
to the list of classes our Expr
declaration derives, along with Show
and Eq
:
{# LANGUAGE DeriveFunctor #}
data Expr a
= Index a a
 Call [a]
 Unary String a
 Binary a String a
 Paren a
 Literal Lit
deriving (Show, Eq, Functor)  fmap for free
In addition, you can derive instances of the Foldable
and Traversable
typeclasses, which provide dozens of of useful functions to access and iterate through an Expr
’s subexpressions—in essence, Expr
now comes with batteries included. Parameterizing Expr
and deriving Functor
, Foldable
, and Traversable
provides us with an embarrassment of helper functions—but this parameterized version of Expr
isn’t quite the same as our previous defintion!
Our first formulation of Expr
, since its recursive subfields were of type Expr
, could represent arbitrarilynested Expr
s, but this new one can’t—it seems like we always have to insert Lit
—to establish the maximum possible depth of a tree of Expr
s:
Expr Lit
represents an expression with no subexpressionsExpr (Expr Lit)
represents expressions with at most one more layer of subexpressions.Expr (Expr (Expr Lit))
represents twolevel expressions, and so on, and so forth.In order for the parameterized definition of Expr
to be equal to our original formulation, we have to assume that there exists a type such that, when substituted for a
in the definition of Expr a
, yields an expression with arbitrarilynested Expr
subexpressions.
type NestedExpr = Expr (Expr (Expr (Expr …)))
But in order for our assumption about the type variable a
to hold true, we need some sort of trick that allows us to represent, in a finite manner, a representation of the type of arbitrarilynested Expr
s.
Consider the Ycombinator. Given a function f that takes one argument, y(f)
represents the result of repeatedly applying f
to itself:
y(f) = f(f(f(f(f ...))))
The sharpeyed will have noticed that the expansion of y(f)
is very similar to our NestedExpr
type above. If we have a Ycombinator embedded entirely in the type system, we can describe the repeated application of Expr
to itself, in a manner identical to how the valuelevel Ycombinator operates on functions, and in turn we can describe an Expr a
where a
represents arbitrarilynested Expr
s.
type Y t = t (t (t (t (t ...))))
This general concept^{3} is known as ‘fixedpoint’: we say that y(f)
is the fixed point (or fixpoint) of the f
function, and that Y Expr
is the fixed point of the Expr
functor. And here’s the kicker—we can build a Ycombinator that works in the type system too, and that’s is how we will express the selfsimilar nature of an Expr
’s subexpressions.
We need a data type Y
that, when given another type f
, wraps an f
whose children are of type (Y f)
. Let’s call it Term
, and let’s call its constructor In
, representing the fact that we are stuffing one level of recursion into a fixed form. In addition, we’ll define an out
function that unwraps a Term
.
data Term f = In (f (Term f))
out :: Term f > f (Term f)
out (In t) = t
It’s illuminating to substitute Expr
in for the type variable in the above definition:
Term Expr = In (Expr (Term Expr))
out :: Term Expr > Expr (Term Expr)
From this definition, we can see that, given a Term Expr
, we can use the out
function to convert it to an Expr
the subexpressions of which are, in turn Term Expr
s. That means that we can unwrap a Term Expr
into an arbitrarilynested Expr
through successive applications of out
: our Term Expr
can expand into an Expr (Term Expr)
, which can expand into an Expr (Expr (Term Expr))
, and so on and so forth. This style of defining recursive types using fixedpoints of functors is an example of codata. A full discussion of the theory behind codata (and the many different forms that codata can take) is, unfortunately, beyond the scope of this article; I recommend this excellent introduction.
At this point, we’re well grounded in defining our data types with fixedpoints of functors. Let’s do something awesome with them.
Consider the notion of the bottomup traversal: specifically, let’s write pseudoEnglish instructions for traversing the fixedpoint of a functor:
To traverse a Term bottomup with a function ƒ:
1. Unpack the term so as to access its children.
2. Recursively traverse each child of the unpacked term with ƒ.
3. Repack the term.
4. Apply ƒ to it.
We have the tools to express each step of this procedure—let’s call it bottomUp.
bottomUp :: Functor a => (Term a > Term a) > Term a > Term a
Given a function fn
from Term
s to Term
s, we’ll unpack the Term
with the out
function, recursively traverse each child of the unpacked term with fmap (bottomUp fn)
, repack the term with the In
constructor, and then simply apply fn
to the result. The fmap bottomUp
call does all the heavy lifting in this function: it captures the act of recursing into each child (if any) of a given functor.
Rather than naming both the fn
function parameter and the Term
parameter, I’m going to define bottomUp
using combinators to join these four invocations—out
, fmap bottomUp
, In
, and fn
. Namely, I’m going to use the >>>
operator, defined in Control.Arrow
, for lefttoright function composition,f >>> g x
is equal to g(f(x))
. Though this style is a bit unconventional—the righttoleft function composition operator, .
, is more common—I’ve chosen to do this because it’s a useful visual indicator of the order in which functions are invoked. (This order will become important later.)
So now let’s write this function, gluing each element together lefttoright with the >>>
operator:
bottomUp fn =
out  1) unpack
>>> fmap (bottomUp fn)  2) recurse
>>> In  3) repack
>>> fn  4) apply
And there it is, our first recursion scheme. In writing bottomUp
we have developed a typesafe and typegeneric combinator for recursively transforming any Functor: whether it’s our Expr
type from earlier, a list, a rose tree, or anything else. This is, frankly, kind of amazing. As such, let’s rewrite our original flatten
function so that it operates on Term
s that wrap arbitrarilynested Expr
s:
flattenTerm :: Term Expr > Term Expr
flattenTerm (In (Paren e)) = e  remove all Parens
flattenTerm other = other  do nothing otherwise
flatten :: Term Expr > Term Expr
flatten = bottomUp flattenTerm
Though our previous definition of flatten
that used apply
to represent its recursion was concise, this is even more elegant: our bottomUp
recusion scheme lets us factor out the recursive parts of this definition entirely. We can focus on the relevant behavior of the flattening function—namely, that it removes all Paren
nodes—and define it in two simple clauses. In addition, recursively invoking this function with bottomUp flattenTerm
is clearer than our prior definitions in that we have made the bottomup nature of this traversal explicit. This is really a remarkable departure from our previous definition of flatten
—it’s hard to imagine how it could be made shorter.
But let’s not rest on our laurels. Let’s consider the steps involved with writing a topdown traversal of a Term
, the obvious analogue to our bottomup traversal:
To traverse a Term topdown with a function ƒ:
1. Apply ƒ to the term.
2. Unpack the term so as to access its children.
3. Recursively traverse each child of the term with ƒ.
4. Repack the term.
These instructions are elegantly symmetrical with the ones for our bottomup traversal—if you read the instructions in reverse and replace occurrences of “unpack” and “repack”, they are identical. And here’s the kicker: our code can capture this. We can express this notion of “reading in reverse” by replacing occurrences of the lefttoright operator >>>
with <<<
, the righttoleft operator^{4}, and we swap “unpack” and “repack” with out
and In
.
topDown, bottomUp :: Functor f => (Term f > Term f) > Term f > Term f
topDown f = In <<< fmap (topDown f) <<< out <<< f
bottomUp f = out >>> fmap (bottomUp f) >>> In >>> f
The fact that we can express the duality between topdown and bottomup traversals merely by “reversing the arrows” that determine our code’s flow, all the while retaining generality and type safety, is nothing short of amazing. That these definitions emerged naturally out of fixedpoints and functors, two concepts central to Haskell and to functional programming in general, is doubly amazing.
Topdown and bottomup traversals are the simplest of recursion schemes—we’ve barely touched the surface of what Bananas, Lenses, Envelopes, and Barbed Wire has to offer us. In the next installment of this series I’ll explore two additional varieties of recursion schemes—and how generalizing each recursion scheme allows us to derive new, moregeneral schemes.
I’d like to thank everyone who read a draft of this entry, especially Nate Soares and Manuel Chakravarty. I’d also like to thank Colin Barrett, who helped me puzzle all this out over latenight Skype sessions. If you have any comments or questions, please drop me a line on Twitter.
In part 2, we go on to define and discuss catamorphisms and anamorphisms.
Rather than tying Bananas, Lenses, Envelopes and Barbed Wire to any particular programming language, Meijer et. al used notation derived from BirdMeertens formalism, a calculus of program construction based on recursion schemes. (Meijer’s Ph.D. thesis discussed compiler specifications using the BirdMeertens formalism). This calculus was also known as “Squiggol”, after its “squiggly” notation. Though this notation is wellspecified, its syntactic constructions, featuring elements such as “banana brackets” and “concave lenses”, is somewhat abstruse.↩
You may be curious as to why Haskell provides both map
and fmap
functions in its Prelude, considering that map
is just a version of fmap
that can only operate on lists. This has indeed been a bone of contention within the Haskell community. As Brent Yorgey, author of the essential Typeclassopedia, put it: “the usual argument is that someone just learning Haskell, when using map
incorrectly, would much rather see an error about lists than about Functor
s.”↩
A complete discussion of the beauty and notability of fixedpoint combinators is beyond the scope of this article: for such explorations, please refer to Raymond Smullyan’s wonderful To Mock a Mockingbird or Reginald Braithwaite’s combinators.info.↩
This function is provided by the Prelude with the .
operator.↩