The Marvellously Mysterious JavaScript Maybe Monad

Written by James Sinclair
on the 7th August 2016

You finally made it. You stepped through the looking glass. You learned functional programming. You mastered currying and composition, and followed the path of functional purity. And gradually, you notice a change in the attitude of the other programmers. There’s ever-so-slightly less disdain in their voice when you talk to them. Every so often you’ll get a little nod when you happen to mention immutable data structures. You’ve begun to earn their respect. And yet…

There’s something they won’t talk about. When they think you’re not in earshot, every so often, you’ll overhear the word ‘monad’ discussed in hushed tones. But as soon as they notice you’re there, they change the subject. One day, you pluck up the courage to ask someone. “What’s this monad thing I keep hearing about?” The other programmer just looks at you. After an awkward silence she simply says “I can’t talk about it”. So you ask another programmer and she replies “Maybe when you’ve learned Haskell.” She walks away sadly, shaking her head.

Mystified, you start searching for answers on the Internet. And at first there seems to be plenty of people eager to explain the mysterious monads. But, there’s a problem. It’s as if every single one of them writes in some kind of code. They talk about applicative functors, category theory, algebraic structures and monadic laws. But none of them seem to explain what monads are for. What do they do? Why do they exist? You keep searching and discover article after article trying to come up with some kind of analogy. Monads are like tupperware. Monads are like trees. Monads are like a bucket line. Monads are like hazmat suits. Monads are like burritos. Comparing monads to burritos considered harmful… It starts to drive you mad.

One day, one of the more junior programmers approaches you, a furtive expression on his face. “Look, you’ve got to stop asking questions about monads, OK? It upsets people. Monads are cursed. It’s not that people don’t want to tell you about them. They can’t.” He looks around again and continues in a hushed tone. “Even ol' father Crockford couldn’t break the curse. He tried. In a keynote conference talk and everything. But it got him. He couldn’t do it. Either you figure monads out or you don’t. No one can help you. That’s just how it works.”

Monads have a bad reputation in the JavaScript community. Douglas Crockford once said that monads are cursed. Once you finally understand monads, you lose the ability to explain monads to others.1 Even experienced functional programmers treat monads with respect. And some of the explanations out there are hard to understand. Especially if they dive straight into category theory. But, if you can understand Promises then you can understand monads.

In this article we will look at just one type of monad: The Maybe monad. Focussing on just one will help explain the basic idea without getting too bogged down in theory. Hopefully it will be enough to set you on the path to enlightenment. I’m still new to Monads myself. Perhaps new enough that the curse hasn’t fully taken hold. Let’s see how it goes…

A quick recap of Promises

In the olden days (before jQuery 3.x), you would often see people make an AJAX call like this:

Promise.resolve($.getJSON('/path/to/my/api'))
.then(function(data) {
// Do something with the data in here.
});

Promise.resolve() was necessary because jQuery’s version of Promises didn’t fully meet the Promises/A+ standard. So, clever people would use the .resolve() method to make the jQuery version into a real Promise.

Now, if I wanted to, I could rewrite the code above so that it uses a named function instead of an anonymous function:

Here we are creating four promises. Each one represents a future value. The intermediate variables aren’t necessary, but they make things clearer. Each .then() call is returning a new promise object. The key thing is that the functions themselves don’t have to know that they’re inside a Promise. They just expect regular values as parameters. This is good because it keeps the functions simple and easy to understand.

Now, if you’ve worked with Promises before, then you may know that Promise.resolve() can work with plain values too, not just AJAX calls. So, returning to the example above, we could swap $.getJSON() with a plain old object:

This creates a promise that resolves straight away with the value of data. What’s interesting to note here is that for this code to work with a regular value instead of an asynchronous value, we didn’t change a thing. All the named functions still take regular variables and return whatever they return.

Monads are like Promises in that they allow us to handle tricky things with a consistent approach. ‘Tricky things’ might include asynchronous data, or null values, or something else entirely. The monad hides away a bunch of the complexity so we don’t have to think about it. This lets us concentrate on writing simple, pure functions that are easy to understand.

A problem to solve

To show how a monad might be useful, let’s consider an example problem. Let’s say we’re working on some code to personalise a website. We want to change the main banner of the site depending on what province (or state) the user lives in. Most of the time, the user data looks something like this:

But this isn’t a great improvement on the pyramid of doom. What was an easy one-line function has transformed into a messy bunch of conditionals. It’s hard to read and makes the purpose of the function less clear. Fortunately, the Maybe monad can help us.

The Maybe monad

In essence, a monad is simply a wrapper around a value. We can create that with an object that holds a single property:

So far, our Maybe wrapper doesn’t do anything for us. If anything, it makes life harder. We want to be able to do things with the value. So, we write a method that will let us get the value and do something with it. But we’ll also put a guard on it, to protect us from those pesky null and undefined values. We’ll call the method map, since it maps from one value to another.2

If any of those prop calls returns undefined then Maybe just skips over it. We don’t have to catch or throw any errors. Maybe just quietly takes care of it.

This looks a lot like our Promise pattern. We have something that creates the monad, Maybe.of(), rather like Promise.resolve(). And then we have a chain of .map() methods that do something with the value, rather like .then(). A Promise lets us write code without worrying about whether data is asynchronous or not. The Maybe monad lets us write code without worrying whether data is empty or not.

Maybe of a Maybe? Maybe not.

Now, what if we got excited about this whole Maybe thing, and decided to write a function to grab the banner URL? We could return a Maybe for that function too:

But now we have a problem. Instead of returning a Maybe with a string inside it, we get back a Maybe with another Maybe inside it. To do something with the value, I would have to add a map inside a map:

getUserBanner(user)
.map(function(m) {
m.map(function(banner) {
// I now have the banner,
// but this is too many maps
}
})

We’re back to another pyramid of doom. We need a way of flattening nested Maybes back down—join them together, you might say. So we create a .join() method that will unwrap an outer Maybe if we have them double-wrapped:

Maybe.prototype.join = function() {
return this.__value;
};

This lets us flatten back to just one layer. So we can add in the join to getUserBanner():

That gets us back to one layer of Maybe. So we can work with functions that pass back Maybes. But, if we’re mapping and joining a lot, we might as well combine them into a single method. It allows us to chain together functions that return Maybes:

With chain() we now have a way of interacting with functions that return other Maybe monads. Notice that with this code, there’s no if-statements in sight. We don’t need to check every possible little thing that might be missing. If a value is missing, the next step just isn’t executed.

But what do you do with it?

You may be thinking, “That’s all well and good, but my banner value is still wrapped up inside a Maybe. How do I get it out again?” And that’s definitely a legitimate question. But let me ask you another question first: “Do you need to get it out?”

Think about it for a moment. When you wrap a value up inside a Promise, you never get it out again. The event loop moves on, and you can never come back to the context you started with.3 Once you wrap the value in the Promise, you never unwrap it. And it’s just fine. We work inside callback functions to do what we need to do. It’s not a big deal.

Unwrapping a Maybe kind of defeats the purpose of having it at all. Eventually though, you will want to do something with your value. And we need to decide what to do if the value is null at that point. With our example, we will want to add our banner to the DOM. What if we wanted to have a fallback banner to use if we get back an empty Maybe? For this we’ll need one more little method:4

Now, if our visiting user happens to come from Nunavut, we can at least show something. And since we’ve got that sorted, let’s aslo grab the banner element from the DOM. We’ll wrap it up in a Maybe too, since it’s possible someone could change the HTML on us.

Now we have two Maybes: bannerSrc and bannerEl. We want to use them both together to set the banner image (our original problem). Specifically, we want to set the src attribute of the DOM element in bannerEl to be the string inside bannerSrc. What if we wrote a function that expected two Maybes as inputs?

This would work just fine, until one of our values was null. Because we’re pulling values out directly, we’re not checking to see if the value is empty. It defeats the entire purpose of having things wrapped in a Maybe to start with. With .map(), we have a nice interface where our functions don’t need to know anything about Maybe. Instead, they just deal with the values they’re passed. If only there was some way to use .map() with our two Maybes…

Let’s rewrite our applyBanner() as if we were just working with regular values:5

Note that we’ve curried the function. Now, what happens if we run .map() with applyBanner()?

bannerEl.map(applyBanner);
// => Maybe([function])

We get a function wrapped in a Maybe. Now, stay with me. This isn’t as crazy as it might seem. The basic building block of functional programming is first-class functions. And all that means is that we can pass functions around just like any other variable. So why not stick one inside a Maybe? All we need then is a version of .map() that works with a Maybe-wrapped function. In other words, a method that applies the wrapped function to our Maybe with a value. We’ll call it .ap for short:

Remember that in the context above, this.__value is a function. So we’re using map the same way we have been all along—it just applies a normal function to a Maybe. Putting it together we get:

var mutatedBanner = bannerEl.map(applyBanner).ap(bannerSrc);

This works, but isn’t super clear. To read this code we have to remember that applyBanner takes two parameters. Then also remember that it’s partially applied by bannerEl.map(). And then it’s applied to bannerSrc. It would be nicer if we could say “Computer, I’ve got this function that takes two regular variables. Transform it into one that works with Maybe monads.” And we can do just that with a function called liftA2 (‘2’ because we have two parameters):

Mission accomplished. We’re now able to pluck the province value from deep within the user preference object. We can use that to look up a banner value, and then apply it to the DOM, safely, without a single if-statement. We can just keep mapping and chaining without a care in the world. Using Maybe, I don’t have to think about all the checks for null. The monad takes care of that for me.

Pointfree style

Now, at this point you may be thinking “Hold on just a second there, Sir. You keep talking about functional programming, but all I see is objects and methods. Where’s the function composition?” And that is a valid objection. But we’ve been writing functional JavaScript all along, just using a different style. We can transform all these methods into plain functions easily:

There are still two impure functions, but customiseBanner is now pointfee. And here’s were things start to get interesting…

Note that when we defined the functional forms of map, chain, ap etc. we didn’t include any mention of Maybe. This means that any object that implements .map() can work with the map function. Any object that implements .chain() can work with chain. And so on. Imagine if we had other objects that implemented these methods…

Pipelines

To show how this works, I’m going to break all the rules for a moment. I’m going to alter the Promise prototype. Note that this is being performed by a trained professional under controlled conditions. Do not try this at home:

Take a moment to look at that processUpdate function again. We’ve defined a pipeline that takes a monad input and then map and lift to transform it. But there’s nothing in the pipeline that assumes we’re working with a Promise. The pipeline would work just as well with our Maybe monad. And, in fact, it would work with any object that meets the Fantasyland Monad Spec.

So, let’s recap what we’ve looked at:

A monad is like a Promise in that you don’t act on a value directly. Instead, we use map to apply a callback, just like then with Promises.

The Maybe monad will only map if it has a value. So, when we map a Maybe, we don’t have to worry about null or undefined values.

If we use monad libraries that conform to a specification, we can compose pipelines. These pipelines can work interchangeably with different types of monad.

Further reading

There is a lot more to learn about monads, and there are many other types of monads besides Maybe. I encourage you to keep reading and find out more. There are three main resources I’ve found helpful:

Slowly, it begins to make sense. You wouldn’t claim to ‘understand’ monads, but you can see how using Maybe might save a lot of effort. So, you slip it in to your next commit, neatly avoiding a couple of null checks. You don’t make a big deal about it. The other programmers don’t say anything, but you know that they noticed. There’s still a lot to learn, but monads are no longer a complete mystery. They’re tools for getting a job done.

OK. OK. Yes, with async/await and generators you can kind–of do something that looks like that, but you definitely can't with ES5. And yet we still managed to get things done before we had all this syntactic sugar. ↩

Note this implementation of .orElse() differs from other libraries. This one expects you to return a regular value, while others expect you to wrap the return value in a Maybe yourself. ↩

Yes, this function is impure and mutates data, but we'll ignore that for now. ↩