avatar

Andres Jaimes

Monoid definition and examples

By Andres Jaimes

- 2 minutes read - 398 words

We are going to cite the definition of a Monoid found on this post:

Conceptually, a monoid is anything that:

- Has a "zero-value": `mempty`, e.g., `0`
- Can be "appended" together: `mappend`, e.g., `+`
- Has an identity with the zero-value: `x + 0 == x, 0 + x == x`
- Is associative: `x + y + z == (x + y) + z == x + (y + z)`

This isn’t limited to just arithmetic with operators like +. You can define whole new types as monoids if they have these characteristics. For example, we can merge together configuration files by using monoid operators (in pseudo-code):

-- Zero Value
mempty == {}

-- Append
{"color": "Blue"} `mappend` {"fontSize": 12} == {"color": "Blue", "fontSize": 12}

-- Identity
{"color": "Blue"} `mappend`               {} == {"color": "Blue"}
               {} `mappend` {"fontSize": 12} == {"fontSize": 12}

-- Associativity
{} `mappend` {"a": "b"} `mappend` {"c": "d"}
  == ({} `mappend` {"a": "b"}) `mappend` {"c": "d"}
  ==  {} `mappend` ({"a": "b"} `mappend` {"c": "d"})

There’s no need to break out the really formal definitions if you’re just trying to get some nice working code.

Borrowing from mathematics is beneficial because we can work with structures that have simple but useful guarantees. In Haskell, you can expect all the above to be true for correct type instances of Monoid, without the implementation details at hand. It’s a core tenet of functional programming to disarm complexity with simplicity.

How do we know we need a Monoid? A simple answer is whenever we need to accumulate or combine two or more items.

Example 1

Going from the previous idea, we can have a basic Monoid in Scala that combines two instances of A by adding its property i.

case class A(i: Int)

object A {
  val empty: A = A(0)
  val combine: (A, A) => A = (a1, a2) => A(a1.i + a2.i)
}

We validate it’s a monoid by checking the monoid rules.

Zero value:

scala> A.empty
val res0: A = A(0)

Append:

scala> A.combine(A(1), A(2))
val res1: A = A(3)

Identity:

scala> A.combine(A(1), A.empty)
val res2: A = A(1)

scala> A.combine(A.empty, A(1))
val res3: A = A(1)

Associativity:

scala> List(A.empty, A(1), A(2)).fold(A.empty)(A.combine(_, _))
val res4: A = A(3)

scala> List(A(1), A.empty, A(2)).fold(A.empty)(A.combine(_, _))
val res5: A = A(3)

scala> List(A(1), A(2), A.empty).fold(A.empty)(A.combine(_, _))
val res6: A = A(3)

References