We started with the tree example, but there is a simpler recursive data structure, a list. You may think of a list as a tree with the branching ratio of one.
The Nil
constructor creates an empty leaf, and the Cons
constructor, besides containing a payload of type C
, acts like a node that has only one child. This is a recursive data structure, so we should be able to apply our algebraic methods to factorize it.
Step one is to define a functor in which we replace the recursive part with an arbitrary type R
The first argument describes the payload, which we'll keep around as a type parameter.
ListF
is indeed a functor
In category theory we would describe this functor as a sum (coproduct) of the unit (terminal object) and a pair (product) of and
An algebra for this functor
is defined by a choice of a carrier A
and an evaluator of the type
Let's see an example of one such evaluator, replacing both C
and A
with Int
You may think of this evaluator as adding the payload x
to the accumulator a
, which is the result of evaluating the tail of the list. An empty list gets assigned a zero value. You can guess that the catamorphism for this algebra will produce the sum of the elements of a list.
A list is a fixed point of this functor
This definition is equivalent to the original recursive definition of a list. To create values of this type, we can define helper functions or smart constructors
For instance, the list of integers [1, 2, 3]
can be defined as
We can apply a catamorphism to it to sum its elements
Since lists are so ubiquitous in functional programming, they deserve special syntax. And because list catamorphisms are very useful, they are defined in libraries as folds.
Notice that a list algebra splits into two components: one is the value of type A
corresponding to NilF
, and the other is a function that combines the two values stored in ConsF x a
. This function has the signature
You may recognize these two components in the definition of foldRight
This is because foldRight
is a list catamorphism.
We are going to use the algebraic methods to implement quicksort. This will require the use of algebras and coalgebras, so we'll build it step by step. The first step is to be able to partion a list in two parts according to a predicate (a yes/no function returning Bool
). The result of the partitioning will be a pair of lists
For simplicity, we've encoded the result type using the built-in implementation of the list. But we'll use the fixed point of ListF
as the argument for recursion.
We'll start by defining the algebra with the carrier TwoLst a
We'll generate this algebra from a predicate. Since ListF
has two constructors, we have to deal with two cases. The NifF
case produces the starting empty accumulator. The ConsF
case adds a new value x
to the accumulator, placing it on the left or on the right, depending on the predicate
We can now pass this algebra to a catamorphism and apply it to an arbitrary list
Since we are operating on lists, the same thing can be implemented using foldr
Here, the second argument passed to foldr
is the NilF
part of the algebra, and the function select
deals with the ConsF
case, combining the new value with the old acumulator to produce the new acumulator.
You can find code for this blog post here