How to Monoid
Table of Contents
1. Motivation
Have you ever wanted to just squash some data? Perhaps we’ve got some stats, some numbers, maybe boolean values, it doesn’t really matter. All we want is to convert a bunch of something to a single something.
2. How Monoids help?
We can create a Monoid for any type by implementing two properties.
- We need a way to combine two elements of type A
We need a neutral element of type A
So that combining this neutral element with any other element yields back the other element.
With just these two properties we can create a pretty neat abstraction to accumulate data!
3. Exploring
3.1. Sum of numbers
Let’s find the two Monoid properties to help us sum numbers.
First we need a way to combine two numbers to get their sum.
…
We know how to do that. That’s addition! We can first the sum of two numbers by simply adding them.
Now we need a neutral element, some element that when added with any other numbers, yields the same number.
…
We also know that. That’s zero! Adding zero to any other number is the same as just the other number.
3.2. All or nothing
Let’s find the two Monoid properties to help us determine if all boolean values are true.
First we need a way to combine two boolean values.
…
We can combine boolean values using the AND or OR operations.
Since we want to make sure all values are true, it makes sense to choose AND, we want to ensure this value AND this other value are true!
Now we need a neutral element, some element that when AND’ed with another boolean value results in true only when both values are true.
…
It sounds like True should be good for that! AND’ing True with any other boolean value will only return true if both values are true.
3.3. Configuration file
Let’s find the two Monoid properties to help us combine configuration files.
In this example configuration files have the following properties:
- whether or not feature A is enabled
- how many seconds to wait to start feature B
…
How would we combine this configuration file?
It seem like we could combine the first property like we did in the previous example, only enable it if all configuration files have it enabled.
The second property could also be combined by adding the seconds on each configuration file, summing numbers, like we did in the first example.
…
Well, we can rest assured! If some object has properties that themselves are handled by a Monoid, then the object itself is also a Monoid.
This means that we don’t need to reinvent a Monoid for configuration files, we can just compose the previous two Monoids we saw to assemble a Monoid to handle the configuration files!
4. Monoids in practice!
To give you a taste of what it’s like to work with Monoid in practice, let’s implement some in Javascript.
Choosing Javascript for it’s popularity and ease of use, really, just press F12 and start typing!
Let’s start with the Sum Monoid:
const Sum = x => ({ x, concat: o => Sum(x + o.x), }) Sum.empty = () => Sum(0)
Here we can apply what we discovered in the first example.
Sum is a function that takes an argument x and returns an object with x, empty and concat properties.
empty refers to the neutral element and concat refers to the combination function.
We can then define a function called sum that takes in a list of Sum objects and returns a Sum object.
const sum = xs => xs .map(Sum) .reduce((acc, curr) => acc.concat(curr), Sum.empty())
First we:
summaps all elements ofxswithSum. e.g [1,2,3] => [Sum(1),Sum(2),Sum(3)]reduce the list of
Sumelements.starting with
Sum.empty()and using the combination function.
Now whenever we want to sum a list of numbers we can use our sum function:
const result = sum([1,2,3,4,5,6,7,8,9,10]) // Sum(55)
4.1. Generalizing
The sum function mentions the Sum Monoid by name,
if we instead pass it as an argument,
we can create a generic function that works for all Monoids!:
const concat = m => xs => xs .map(m) .reduce((acc, curr) => acc.concat(curr), m.empty())
We can then define sum in terms of concat:
const sum = concat(Sum)
4.2. Other examples
4.2.1. Product of numbers
In the same way we defined a Monoid to sum numbers, we can define one to multiply them as well!:
const Product = x => ({ x, concat: o => Product(x * o.x), }) Product.empty = () => Product(1)
We can also define a function called product in terms of concat:
const product = concat(Product)
And use it like so:
const result = product([1,2,3,4,5]) // Product(120)
This pattern continues with the other Monoid definitions.
4.2.2. All or nothing
const All = x => ({ x, concat: o => All(x && o.x), }) All.empty = () => All(true) const all = concat(All) all([true, true, false]) // All(false) all([true, true, true]) // All(true)
4.2.3. Any
const Any = x => ({ x, concat: o =>Any(x || o.x), }) Any.empty = () => Any(false) const any = concat(Any) any([true, false, false, false]) // Any(true) any([false, false, false]) // Any(false)
5. Conclusion
The concept of having a nice abstraction to squash data is pretty cool to me and I hope it seems cool to you too!
Knowing more about Monoids allowed us to define the concat function,
which abstracts this accumulation step and helps us focus on the idea of combining this different types.
I hope this little note made this seemingly scary mathematical term a bit less scary and more approachable, and maybe even fun to explore!