Blog

Monoidal behavior in JavaScript


The term monoid is a bit aggressive and intimidating. In practice, though, it’s actually a very simple concept with very powerful applications not only in mathematics but in application code and programming language design. To begin, let’s define another stealthy concept — a Semigroup.

Semigroup
Semigroups are more of a recent concept relative to its monoid sibling. Both are algebraic structures. An algebraic structure represents a set of possible elements (e.g the characters in a string or the positive natural numbers) with one or more operations (functions) that abide by certain laws (axioms).
In the case of a semigroup, there is a set of elements with only one binary operation that closes over or restricts the elements contained in the said group (hence, “semi”); this operation by contract must be associative. Like we learned in algebra, associative means that you can group the operations different ways and the result is the same:

Without going into much detail here, I just want it to point out that if the operations performed on these elements could be carried out in this fashion, then that would imply that each one of the benefits from a certain level of isolation. That is to say, the order doesn’t matter (namely, run b and c, then combine it with a, or run a and b and combine that result with c) so long as the sequence of the operands in question remains the same. This implies that how you obtain these elements (if a, b, c were the results of some arbitrary computations) could be parallelized. I’ll come back to this idea of parallelism later on, for this problem has already been solved using a monoid in JavaScript.

Monoid
Monoids are semigroups.
As such they are algebraic structures with a Set and a single binary associative (additive) operation (concat). In addition, its set has a single identity element (also known as empty or neutral element) with the following laws. For any monoid M:
m.concat(M.empty()) leaves m unchanged (right identity)
M.empty().concat(m) leaves m unchanged (left identity)
We’ll be proving this law as well as associativity. In other words, monoids are commutative with respect to concat and its neutral element, irrespective of what they are.

Mixins
A mixin is a way to extend an object’s prototype with some arbitrary behavior — think traits.
Note: In this case, “functional” has nothing to do with functional programming; rather it means to literally use a function to mix-in the behavior and get around some limitations with object mixins using Object.assign.

What about async?
The reality is, however, that in JavaScript 95% of the time you’re dealing with an asynchronous API of some sort, whether it’s file IO, HTTP call, interacting with the console, etc. So can monoids help us here as well?
As you saw earlier, functions are monoidal over composition, so whether they are asynchronous is beside the point. All we need to do is make sure that we use an asynchronous abstraction that is also monoidal under some form of “concatenation.” How do we concatenate events separated in time? “Do this then do that…” How about promises? If asynchronous functions are what we want, let’s implement the Chain monoid around promises and composition.

As identity I choice a promise that resolves right-away. Just so that we don’t mess with the Function.prototype, consider this simple wrapper.

Similar to the synchronous case, the order I combine these doesn’t matter, the result is the same:

That’s quite impressive because now I don’t have to really worry about the timing of these functions or deeply nested callbacks, they are still executing in the same manner.

In fact, this monoid pattern has penetrated deeply into the design of Promise. Coming back to the relationship between associativity and concurrency I alluded to earlier, you can see that reflected here:

Last but not least: Fold
Endowing classes with monoid behavior has other benefits. One such benefit is the ability some abstract data type, like a list, to be “folded” or “reduced” to a single value. You’re already familiar with this concept in JavaScript arrays:

More specifically, a fold takes two parameters: an accumulator callback function and an initial value. Hence, we can make a direct parallel to monoids as naturally foldable with concat as the accumulator and empty as the initial value. And because this is the contract that all monoids abide by, we can generalize a single fold function to work with all of them!

This will work in all cases with elements of type M, irrespective of what M is because reduced only relies on the monoid contract.

And it doesn’t take a ninja to walk the path of the monoid!

To conclude, remember a monoid is an algebraic structure with just an additive operation with an identity object. While very simple in structure, it’s used is immensely powerful and has inspired lots of really great API designs over the years.






FREE Consultation

for First 100 Entrepreneurs