Skip to the content.

Initial F-Algebra of monoid and free monoid, part 1

Monoid has two operations mappend and mempty that satisfy some conditions such as associativity law and unit law. When you ignore these conditions, you can express monoid as a combination of these two operations.

data Monoid a = Append a a
              | Empty

instance Functor Monoid where
    fmap f (Append n m) = Append (f n) (f m)
    fmap _ Empty = Empty

You can have functions of type Monoid a -> a to evaluate this monoid. For example, sumIntAlg evaluates it with additions, and productIntAlg evaluates it with multiplications.

sumIntAlg :: Monoid Int -> Int
sumIntAlg (Append m n) = m + n
sumIntAlg Empty = 0

productIntAlg :: Monoid Int -> Int
productIntAlg (Append m n) = m * n
productIntAlg Empty = 1

Let’s evaluate some values with them.

v1, v2 :: Int
v1 = sumIntAlg (Append 1 2)
v2 = productIntAlg (Append 1 2)

A set of Monoid, Int and a function of type Monoid Int -> Int is called F-Algebra.

sumIntAlg and productIntAlg aren’t that interesting, but we can build Monoid on Monoid Int as well.

sumMonoidIntAlg :: Monoid (Monoid Int) -> Monoid Int
sumMonoidIntAlg (Append m n) = Append (sumIntAlg m) (sumIntAlg n)
sumMonoidIntAlg Empty = Empty

We can now evaluate a monoid with depth-one trees with sumMonoidIntAlg.

v3 :: Int
v3 = sumIntAlg $ sumMonoidIntAlg $ Append (Append 1 2) Empty

We can repeat it to evaluate a monoid with depth-two trees and so on.

sumMonoidMonoidIntAlg :: Monoid (Monoid (Monoid Int)) -> Monoid (Monoid Int)
sumMonoidMonoidIntAlg (Append m n) = Append (sumMonoidIntAlg m) (sumMonoidIntAlg n)
sumMonoidMonoidIntAlg Empty = Empty

v4 :: Int
v4 = sumIntAlg $ sumMonoidIntAlg $ sumMonoidMonoidIntAlg $ Append (Append (Append 1 2) (Append 3 4)) Empty

When you repeat this process infinite times, you’ll get a point where repeating it one more time doesn’t change its structure. We can express it using Fix below.

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

out :: Fix f -> f (Fix f)
out (In f) = f

When you have a value In Empty :: Fix Monoid, applying In one more time like In (Append (In Empty) (In Empty)) won’t change its type. It’s still Fix Monoid.

An evaluator of Fix Monoid is In. It takes Monoid (Fix Monoid) and returns Fix Monoid. A set of Monoid, Fix Monoid and In is called an initial F-Algebra. This is because it’s an initial object of a category of F-Algebra where an object is a pair of a and Monoid a -> a. Since a pair of Fix Monoid and In is an initial object of this category, there should be a unique morphism from it to any other objects.

This morphism is a homomorphism because it preserves a structure of F-Algebra. Let’s take Monoid Int and sumIntAlg as an example.

                    fmap homInitialToSumInt
Monoid (Fix Monoid)           →→→           Monoid Int
         ↑ ↓                                     ↓
     out ↑ ↓ In                                  ↓ sumIntAlg
         ↑ ↓                                     ↓
     Fix Monoid               →→→               Int
                      homInitialToSumInt

We’ll define a morphism homInitialToSumInt from Fix Monoid to Int. First, we’ll apply out to get Monoid (Fix Monoid). Then, apply fmap homInitialToSumInt itself to get Monoid Int. Finally, we’ll apply sumIntAlg to get Int.

homInitialToSumInt :: Fix Monoid -> Int
homInitialToSumInt = sumIntAlg . fmap homInitialToSumInt . out

Fix Monoid has an infinite nested Monoid a, and you can think homInitialToSumInt applies itself to the next level recursively using fmap.

What monoids can we have with these definitions? For instance, we can have Fix Monoid like these.

m0, m1 :: Fix Monoid
m0 = In Empty
m1 = In (Append (In Empty) (In (Append (In Empty) (In Empty))))

You can apply homInitialToSumInt to evaluate them.

i0, i1 :: Int
i0 = homInitialToSumInt m0
i1 = homInitialToSumInt m1

Of course, i0 and i1 will evaluate to 0. This isn’t very interesting. We’ll see how we can bring actual Int values into this in the next post.

By the way, homInitialToSumInt can be generalized to any F-Algebra of type Monoid Int -> Int like this.

homInitialToInt :: (Monoid Int -> Int) -> Fix Monoid -> Int
homInitialToInt alg = alg . fmap (homInitialToInt alg) . out

Then, you can generalize to any F-Algebra of type Monoid a -> a.

hom :: (Monoid a -> a) -> Fix Monoid -> a
hom alg = alg . fmap (hom alg) . out

Finally, this can be generalized to any functor f, and it’s called a catamorphism.

cata :: Functor f => (f a -> a) -> Fix f -> a
cata alg = alg . fmap (cata alg) . out