A Curious JavaScripter’s Guide to Monad Pattern
There are times when, on the journey to enlightenment, one comes across a programming concept so mind-bending, and yet so simplistically elegant that they weep with inner joy. I mean sure, loops are great. But recursion is another beast entirely; allowing you to express yourself so tersely both in code and on paper that life without it seems dull, if not handicapped. Yet I, along with probably many others, struggled to apply recursion intuitively to solve problems when the concept was first introduced to me.
The Monad pattern is a similarly invigorating concept. One that requires a good bit of work to grok, but also adds a sophisticated construct to your arsenal. It can also help reduce moving parts in program, resulting in code that’s potentially easier to read. So let’s explore a bit to see what it’s about.
Programming with context
To really understand what monads are and why they’re useful, we need to take a short detour to first explore contexts. Think of context as special powers attached to a variable which can be used in subtle ways as its value changes.
For example, imagine if our functions accept a value, knowing that there may not be a value at all. This is not exactly a new thing. We’ve been checking for null inputs since before dinosaurs.
However, what we’ve generally been doing was sprinkling if-else
at random places to make sure nulls and invalid values are handled. This example is about abstracting this type of check to a higher level so that we may express our logic more clearly. In other words, providing a home for most boilerplate if-else
checking.
Observe the following snippet:
class Maybe { constructor (type, value) { Object.extend(this, {type, value}) } static just (val) { return new Maybe('just', val); } static nothing () { return new Maybe('nothing', null); } } Maybe.just('Hello'); // Represents a concrete value Maybe.nothing() // Represents an invalid
So Maybe
is a class, while just
and nothing
are its two constructors. All concrete values can be thought of as wrapped inside a just
while nothing
represents nothing (literally!). We’ve used type
to differentiate between the two. However, type
is not the context. It’s just a representation of the idea that an object of Maybe can represent a value or absence of it. Take a moment to re-read the previous sentence again. When we write our code to deal with objects of Maybe
, we’ll know that the object may actually be nothing
.
If you found the above text hard to grok, just think of nothing
as a glorified null
for now. One which we define ourselves instead of (sometimes) incorrectly assuming null
to always be invalid. just
, therefore, is everything that’s not nothing
. i.e. an actual value.
So, with our custom wrappers to represent valid and invalid values, let’s see how our lives have been made a little easier.
Say hello to chain
a.k.a. the life-blood of the monad pattern
Chain
is a method which, wait for it, allows a chain of operations in sequence. What’s the rub? It also takes care of handling invalid or nothing
values while it’s at it to make sure our code doesn’t crash and burn if users start sending nyan-cats through an email field.
We edit our Maybe
class and add a chain method thus:
class Maybe { // ... original definition of Maybe chain (fn) { if (this.type === 'nothing') { return Maybe.nothing(); } else { return fn(this.value); } } }
Example Time!
We have a series of string manipulation methods, all of which need to check for certain invalid conditions to be a little robust.
const titleize = input => { if (typeof input !== 'string' || input.length === 0) { return ''; } return input[0].toUpperCase() + input.slice(1); } const exclaim = input => { if (typeof input !== 'string' || input.length === 0) { return ''; } return input + '!'; } const someString = 'kent is superman'; exclaim(titleize(someString)); // 'Kent is superman!' const someInvalid = 123; exclaim(titleize(someInvalid)); // ''
Even for this small example, notice how adding type safety led to seemingly repetitive code. Let’s lift that up in chain
method. Here’s the finished chain
method with our custom logic ready for primetime.
class Maybe { // ... original definition of Maybe chain (fn) { if (this.type === 'nothing') { return Maybe.nothing(); } else if (typeof this.value !== 'string' || this.value.length === 0) { return Maybe.nothing(); } else { return fn(this.value); } } } const titleize = input => { return Maybe.just(input[0].toUpperCase() + input.slice(1)); }; const exclaim = input => { return Maybe.just(input + '!'); }; const someString = Maybe.just('kent is superman'); someString .chain(titleize) .chain(exclaim); // just 'Kent is superman!'; const someInvalid = Maybe.just(123); someInvalid .chain(titleize); .chain(exclaim); // nothing const someNothing = Maybe.nothing(); someNothing .chain(titleize) .chain(exclaim); // nothing
Observe how we removed the type and length checking and instead made the return value a Maybe object. Also, notice the pattern of execution went from function1(function2(input))
to input.chain(function1).chain(function2)
which follows an arguably more natural flow. This might seem familiar to those who use promises to serialize async operations. They follow the monad pattern too, btw. If only javascript supported custom definitions for operators, our final code could’ve looked like input |> function1 |> function2
. Nice, eh?
Wrap-Up
Note that the monad pattern is an abstraction like most other patterns, not a defined block of code. Case in point, what we saw above isn’t the high and low of it. It was a single example of how the monad pattern can be applied to deal with invalids. There’s lots more ways to exploit monads for a ridiculous variety of tasks. Feel free to google Either Monad
, Logger Monad
or Parser Monad
to explore a few more.
Hopefully this post gave you insight to leverage the Monad pattern. I’d be happy to read about you using it in your work or personal projects. Share your thoughts in the comments below. Use it to direct any hate, praises, WTFs too 🙂
Note:
chain
is actually called bind
in monadic nomenclature. But since the word is already in javascript’s dictionary for an entirely different purpose than what we used chain
for, it was rechristened.bind
(or chain
from our code). It works similarly though.Akash Agrawal
Related Posts
-
Quick Practical Guide for ES6
In this post we are going to examine what new features arrived in ES6 and…
-
Quick Practical Guide for ES6
In this post we are going to examine what new features arrived in ES6 and…