This post is part 2 of a series on Algebras in Software Development. You can find part 1 here: Day 69: on algebras, part 1 • Daniel Bolivar
In the last post we saw a couple of Javascript operations and how they fulfilled the laws of Associativity and Identity. To refresh a bit:
x + (y + z) === (x + y) + z
. x * 1 === x
. Now, and here’s when it gets fun, look at this: "Hello" + " World!"
. We can figure out 3 things about it just from looking at it:
+
means it’s an operation. “Hello”
and “World!”
) which means it’s binary. Now, let’s see this definition:
Binary associative operation with an identity.
Right? So, our string concatenation is a binary associative operation with an identity. And you know what else is a binary associative operation with an identity?
That’s right, you heard it here first, we just defined Monoids. And also realized in the process that concatenating strings is a Monoid, same as addition and multiplication. There are a couple of important things to keep in mind here:
What that means exactly is that, we can follow the laws that define a Monoid and abstract that behaviour into our code. That means that, every time we see something defined as a Monoid, we know what it can and cannot do. But words are not enough, so let’s look at some code:
const Addition = x => ({
take: () => x,
mappend: y => Addition(x + y.take()),
inspect: () => `Addition(${x})`,
toString: () => `Addition(${x})`
});
const additionEmpty = Addition(0);
const one = Addition(1);
const two = Addition(2);
const three = Addition(3);
const assoc = one
.mappend(two.mappend(three))
.toString() ===
one
.mappend(two)
.mappend(three)
.toString(); // true
const leftIdentity = one.mappend(additionEmpty).toString() === one.toString(); // true
const rightIdentity = additionEmpty.mappend(one).toString() === one.toString(); // true
There’s a lot going on there, so let’s look at it in smaller chunks:
const Addition = x => ({
take: () => x,
mappend: y => Addition(x + y.take()),
inspect: () => `Addition(${x})`,
toString: () => `Addition(${x})`
});
Addition is a function that takes a value x
and returns an object with 4 properties:
Most of this object is just fluff. Meaning, in other languages like Haskell, we wouldn’t need take, inspect, or toString. The only really important function here is mappend.
Next:
// …
const additionEmpty = Addition(0);
const one = Addition(1);
const two = Addition(2);
const three = Addition(3);
Four assignments here. The first one, additionEmpty
is the value that fulfils the law of Identity for our Addition object, for this case, 0
. The rest are to use them in the law proving part.
// …
const assoc = one
.mappend(two.mappend(three))
.toString() ===
one
.mappend(two)
.mappend(three)
.toString(); // true
A bit of a mouthful, but it’s basically proving that the law of Associativity holds true for Addition. The result would be, in both cases, Addition(6)
.
// …
const leftIdentity = one.mappend(additionEmpty).toString() === one.toString(); // true
const rightIdentity = additionEmpty.mappend(one).toString() === one.toString(); // true
We use our additionEmpty
value to test wether the law of Identity holds true as well. In this case, we check wether it’s true regardless of where we place the additionEmpty
value.
This piece of code creates an object representation of the +
operation by staying true to the laws that define a Monoid.
Granted, it doesn’t seem too useful at the moment. That’s because we have only defined one operation and we don’t really use it anywhere. But imagine a codebase where you could know for certain that some things were Monoids and fulfilled these rules. The code you write could use this to its advantage by using that certainty. This would effectively mean we can harness the strength of Mathematics and make our code more robust, and easier to reason about.
We never start from scratch when we write code. The languages we work in are the creations of great minds that came before us. The same happens if we use Algebras like Monoids. And you know what they say about standing on the shoulders of giants, right?
Tomorrow, we’ll look into other JS implementations of Monoids and dig a bit deeper into why these kind of structures are so useful.