Lance Myers


Recursion Schemes and Algebras For Endofunctors

To begin, consider a functor F:CCF : \mathsf{C} \to \mathsf{C}. An object AA of C\mathsf{C} and a morphism α:F(A)A\alpha : F(A) \to A comprise an FF-algebra. A morphism ff bewtween algebras (A,α)(A, \alpha) and (B,β)(B, \beta) is a map ABA \to B such that fα=βF(f)f \circ \alpha = \beta \circ F(f).

In Haskell we have

newtype Algebra f a 
= Algebra { runAlg :: Functor f => f a -> a }

By carefully choosing FF, we can encode the structure of monoids. Define F(x)=1+X2F(x) = 1 + X^2, then an algebra of FF is a map 1+X2X1 + X^2 \to X, which is a pair of maps 1X1 \to X and X2XX^2 \to X, identity and multiplication respectively.

data MonoidF a 
= Mempty
| Mappend a a
deriving Functor


class Monoid' a where
monoidCarrier :: Algebra MonoidF a


instance Monoid a => Monoid' a where
monoidCarrier
= Algebra $ \case x of
Mappend x y -> mappend x y
Mempty -> mempty


instance Monoid' a => Monoid a where
mappend x y
= runAlg monoidStructure (Mappend x y)

mempty
= runAlg monoidSstructure Mempty

Lambek’s Lemma

Lemma. If FF has an initial algebra (A,α:F(A)A)(A, \alpha : F(A) \to A) then α\alpha witnesses an isomorphism AF(A)A \cong F(A).

Proof. Consider the FF-algebra (F(A),F(α))(F(A), F(\alpha)). There exists a unique algebra morphism $f : A \to F(A) $. Then αf=1A\alpha \circ f = 1_A by the uniqueness provided by initiality of AA. Since ff is an FF-algebra map, we have $ f \circ \alpha = F(\alpha) \circ F(f) $ which by functoriality is equal to identity.

This theorem says that if FF has an initial algebra, then FF has a fixed point μF\mu F. As an example, consider the functor FA(X)=1+A×XF_A(X) = 1 + A \times X. Then we have

\mu F_A = F_A(\mu F_A) = \cdots = 1 + A \times (1 + A \times ( \dots )) = 1 + A + A^2 + A^3 + \cdots

This is simply the free monoid on AA. We have some degree of choice when it comes to encoding the fixed point. Later we will find another fixed point, that does not necessarily coincide.

newtype Fix f = Fix (f (Fix f))

In code this looks like the following.

newtype Mu (f :: * -> *) 
= Mu { unFix :: Functor f => f (Mu f) }

data FreeMonoidF a x
= Nil
| Cons a x

instance Functor (FreeMonoidF a) where ...

type FreeMonoid a = Mu (FreeMonoidF a)

-- exhibiting an isomorphism between [a] and FreeMonoid a
fromList :: [a] -> FreeMonoid a
fromList [] = Mu Nil
fromList (x:xs) = Mu (Cons x $ fromList xs)

toList :: FreeMonoidF a -> [a]
toList (Mu Nil) = []
toList (Mu (Cons x xs)) = x : (toList xs)

Given an algebra α:FA(A)A\alpha : F_A(A) \to A, there is a unique map from the initial algebra FA(μFA)μFAF_A(\mu F_A) \to \mu F_A to α\alpha. That is, we can construct a morphism μFAA\mu F_A \to A from a monoid on AA. This is the well known mconcat in haskell. If we instead have the algebra β:FA(B)B\beta : F_A(B) \to B then we can construct a morphism μFAB\mu F_A \to B. This is the slightly more general fold.

There is an obvious generalization for an arbitrary functor FF, and we call that a catamorphism. The implementation can be easily read off of the diagram.

{-
F(A) <---- F(Mu(F))
| F(f) | ^
| | | unFix
V f V |
A <---- Mu(F)
-}

cata :: Functor f => Algebra f a -> (Mu f -> a)
cata alg = let f = runAlg alg . fmap f . unFix in f

Dually, …

Now we can slap a “co” in front of everything and turn around the arrows. An FF-coalgebra is an object AA of C\mathsf{C} and a morphism α:AF(A)\alpha : A \to F(A).

newtype Coalgebra f a 
= Coalgebra { runCoalg :: Functor f => f a -> a }

Lemma. If FF has a terminal coalgebra (A,α:AF(A))(A, \alpha : A \to F(A)) then α\alpha witnesses an isomorphism AF(A)A \cong F(A).

The proof strategy is the same as before.

Proof. Consider the FF-coalgebra F(A),F(α):F(A)F(F(A))F(A), F(\alpha) : F(A) \to F(F(A)). There is a unique coalgebra map f:F(A)Af : F(A) \to A. Since α\alpha is itself a coalgebra map, we have by terminality fα=1Af \circ \alpha = 1_A. Then αf=F(f)F(α)=F(fα)=1F(A)\alpha \circ f = F(f) \circ F(\alpha) = F(f \circ \alpha) = 1_{F(A)}.

If we return to our example of FAF_A, then we see that given a coalgebra B,β:BFA(B)B, \beta : B \to F_A(B) we have a map BμFAB \to \mu F_A. This is unfold in haskell. Interestingly, while fold has found its way to many other languages, often under the name reduce, its dual, unfold, is relatively obscure.

The natural generalization for an arbitrary functor FF is called an anamorphism. Again, the implementation can be read directly off of the diagram.

{-
A -----> Mu(F))
| f |
| |
V F(f) V
F(A) -----> F(Mu(F))
-}

ana :: Functor f => Coalgebra f a -> (a -> Mu f)
ana coalg = let f = Mu . fmap f . runCoalg coalg in f

A hylomorphism is an anamorphism followed by a catamorphism. We might expect an implementation like hylo alg coalg = cata alg . ana coalg, but we can do better. A hylomorphism builds up a big structure of values, then folds it back down. The full intermediate structure is unnecessary, so we should be able to avoid building it up.

Once again, the implementation is the recursive equation read directly off of the diagram.

{- F(A) ---> F(muF) ---> F(B)
^ |
| |
| V
A ---> muF ---> B
\___________________/^
h -}


hylo :: Functor f => Algebra f b -> Coalgebra f a -> (a -> b)
hylo alg coalg = let h = runAlg alg . fmap h . runCoalg coalg in h

This sort of implementation is always charming to me. We do not need to think about what the function is doing, we simply write down the equation it satisfies, and it just works!

Further Reading