A Monoid is one of the most basic algebraic structures that has many instances and applications in computer science and (functional) programming.

And despite it’s scary mathy name it’s really easy to grasp and you are using them already!

first example

A typical example of a Monoid for functional programmers are lists over some type. Those are the elements of our monoid – but we need more:

binary operation: the concatenation of two lists will do (it’s @ in F#)

in F#) neutral element: the empty list.

We are programmers so let’s warp this into a record:

type Monoid<'a> = { neutral : 'a op : 'a -> 'a -> 'a } module Examples= let listM<'l> : Monoid<List<'l>> = { neutral = [] op = fun a b -> a @ b }

Definition and Axioms

Of course it is math so there is a formal definition:

A Monoid \( (M, \cdot) \) is a set \(M\) equipped with a binary operation: \( \cdot : M \times M \rightarrow M \) that satisfies the following axioms:

the operation is associative . For each \(a\), \(b\) and \(c\) we can choose from \(M\) the following holds: $$( a \cdot b ) \cdot c = a \cdot (b \cdot c)$$

. For each \(a\), \(b\) and \(c\) we can choose from \(M\) the following holds: $$( a \cdot b ) \cdot c = a \cdot (b \cdot c)$$ there is a neutral Element \(\mathbf{e} \in M\): For each \(a \in M\) $$ \mathbf{e} \cdot a = a \cdot \mathbf{e} = a $$.

Let’s use FsCheck to see if we can find any issues with those Axioms and our lists:

module ``check Monoid axioms`` = type T = List<int> let M = Examples.listM<int> let Z = M.neutral let (++) = M.op [<Property>] let ``Z is the neutral element``(v : T) = Z ++ v = v && v ++ Z = v [<Property>] let ``the operation is commutative``(a : T, b : T, c : T) = a ++ (b ++ c) = (a ++ b) ++ c

The way I prepared this will make it easy to check other instances for us later – we just have to change the definition T and M at the beginning of the module.

Sure enough all tests passes but for list we can really be sure:

a List is a monoid (proof)

remark

You can skip this if you want – it will not help you gain insight into Monoids – see it as a bonus.

So let’s proof the axioms but for this we need to know how @ is implemented. To demonstrate this let’s say it’s implemented like this:

let rec concat (ls : 'a list) (ls' : 'a list) = match ls with | [] -> ls' | (l::ls) -> l :: concat ls ls'

Now the proof is just an exercise in equational reasonings.

Neutral element

Let’s start with the axiom concerning the neutral element: For every list ls we have

[] ++ ls = concat [] ls { first case in concat } = ls

So this is easy. For the other case we check ls using structural induction:

For ls = [] it’s again the first case in concat :

ls ++ [] = concat [] [] = [] (== ls)

And for ls = l :: ls' we can argue like this:

ls ++ [] = concat (l:ls') [] { second case } = l :: concat ls' [] { induction } = l :: ls' = ls

And we see that [] indeed satisfies this axiom.

Associativity

For the other axiom again using induction on the first list:

If xs and ys are any lists and if ls = [] then if we compare the left-side of the axiom:

[] ++ (xs ++ ys) { we just saw } = xs ++ ys

with it’s right-side

([] ++ xs) ++ ys { using the other axiom again } = (xs) ++ ys = xs ++ ys

we see that they match.

Now if ls = l::ls' then:

The left-side is

(ls ++ xs) ++ ys = ( (l::ls') ++ xs ) ++ ys { second case of concat } = (l :: (ls' ++ xs) ) ++ ys { second case of concat } = l :: ( (ls' ++ xs) ++ ys ) { induction } = l :: (ls' ++ (xs ++ ys) ) { second case of concat in the other direction } = (l::ls') ++ (xs ++ ys) = ls ++ (xs ++ ys)

and the last line is just the right side.

This concludes our little proof.

more examples

Of course there are many examples for monoids:

integers, addition and 0 as the neutral element:

let intPlus : Monoid<int> = { neutral = 0I op = (+) }

integers, multiplication and 1 as the netural element:

let intMult : Monoid<int> = { neutral = 1 op = (*) }

booleans, conjuction (&&) and true as the neutral element:

let binCon : Monoid<bool> = { neutral = true op = (&&) }

functions from a type to itself with composition and id as it’s neutral element:

let funs<'a> : Monoid<'a -> 'a> = { neutral = id op = (>>) }

exercises

Check that intPlus and intMult are indeed Monoids (use the FsCheck-properties or as a bonus eq. reasoning – hint: better ignore overflows if you want to keep sane)

and are indeed Monoids (use the FsCheck-properties or as a bonus eq. reasoning – hint: better ignore overflows if you want to keep sane) Check binCon – can you find another Monoid using booleans as it’s set?

– can you find another Monoid using booleans as it’s set? Can you rewrite the FsCheck-properties to show that funs is a Monoid?

is a Monoid? Strings and string-concatenation form a monoid too but there is something fishy with .net-strings here – can you see it? (Hint: FsCheck will help you out if not) – How can you fix this?

free monoids (bonus)

Free objects are a big deal in category theory and mathematics in general and I only want to mention it here as we’ve already seen the free Monoid in disguise – the list-monoid from the very beginning.

In a sense free just means that it contains all there is to know about monoids: if M is another monoid over the type 't then there is a simple monoid-homomorphism between M and listM<'t> :

let rec hom (M : Monoid<'t>) : ('t list -> 't) = function | [] -> M.neutral | (t::ts) -> M.op t (hom M ts)

If you look really carefully this is just a fold and if you want you can allow an additional map to get into other monoids as well:

let hom (M : Monoid<'m>) (f : 'l -> 'm) (ls : 'l list) : 'm = List.foldBack (f >> M.op) ls M.neutral

If you follow this path it will lead you to Haskells Foldable and MapReduce (to a certain extent).