Skip to Content

Day 70: on Algebras, part 2

Posted on 5 mins read

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:

  • Associativity ➡ We can reorganise the operation and still get the same results. That usually means changing the order of parenthesis in our operation. In code, with addition it means: x + (y + z) === (x + y) + z.
  • Identity ➡ This law is fulfilled when there exists a value in our set (possible inputs for the operation) that turns it into the Identity Function. For multiplication, that value would be 1, since 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:

  • The + means it’s an operation.
  • There are 2 things (“Hello” and “World!”) which means it’s binary.
  • It’s string concatenation, which in the last post we saw fulfils the laws of Associativity and Identity.

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?

Monoids

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:

  • A Monoid is an operation. We’re not talking about classes, types or objects just yet.
  • If we build types that fulfil the same laws, we can be certain that they will behave like Monoids do.

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:
    • take ➡ Javascript only. Used to get the value inside of the Addition object.
    • mappend ➡ (from monoidAppend). It’s a function that defines how any two instances of this object can be joined together. For this case, it’s by adding them both up.
    • inspect ➡ Util function to get Node to pretty print our objects when we log them to the console.
    • toString ➡ Util function so we can compare two instances of Addition (JS can’t do object equality).

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.

Why is this useful again?

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.

comments powered by Disqus