By Sean Voisen
Monads: they’re incredibly useful, and also a little intimidating. Beginner functional programmers often cringe when they hear the term. JavaScript legend Douglas Crockford once said that monads are cursed – that once you understand monads for yourself, you lose the ability to explain them to others. In the programming language F#, monads are called “computational expressions” mostly so people aren’t scared away.
But I think all this fear and mysticism around the dreaded “M-word” need not be so. So in this post, I’m going to attempt to put a small crack in the curse, not by attempting to explain all of monad theory in general, but instead by thoroughly diving into a concrete example of a monad in a specific language: the Maybe monad in JavaScript 1.
If you’ve been putting off learning about monads – or maybe have never even heard of them until now – then this post is for you. It will provide just enough material to give you a sense of what monads are and what they can do. From there you should have a solid stepping off point from which you can jump into reading something like this without trepidation.
If you’re a seasoned developer, chances are you have already used monads in your daily practice without even realizing it. For instance, a jQuery Deferred object is a monad. So is jQuery Ajax, as well as Bacon.js EventStream. So this shouldn’t be too hard to follow.
On occasion, I will reference similarities between the JavaScript example and its counterparts in the programming language Haskell. I do this only because most formal literature on monads references Haskell, and it helps to become familiar with the language. Feel free to skip these parts if you prefer.
Maybe We Have a Problem
After the Identity monad, the Maybe monad is perhaps one of the simplest examples of a monad available. It’s also quite useful.
The Maybe monad provides an elegant solution to the common problem of multiple nested null
checks in code. Consider the following trivial example:
var person = {
"name":"Homer Simpson",
"address": {
"street":"123 Fake St.",
"city":"Springfield"
}
};
if (person != null && person["address"] != null) {
var state = person["address"]["state"];
if (state != null) {
console.log(state);
}
else {
console.log("State unknown");
}
}
All those null
checks are fairly ugly. They’re tedious to write and annoying to read, an unfortunate side-effect of working in a language in which null
was implemented rather poorly. Is there perhaps a way to factor them out? Yes there is.
Maybe We Have a Solution
What we want is to embed the computation of != null
into a function, type or class that we can easily re-use so that we don’t have to spatter our code with null
checks. This is exactly what the Maybe monad provides. In Haskell, the definition of type Maybe is rather succinct:
data Maybe t = Just t | Nothing
All this means is that an object of type Maybe either has some value (Just t
) or no value (Nothing
). What is meant by Nothing
depends on the context. In JavaScript the only things that mean “nothing” are null
and undefined
. But as you will see, with the Maybe monad, we can change the semantics of “nothing” to suit our needs.
We can begin to model the Haskell definition in JavaScript as follows:
Maybe = function(value) {
var Nothing = {};
var Something = function(value) {
return function() {
return value;
};
};
if (typeof value === 'undefined' || value === null)
return Nothing;
return Something(value);
};
Now we have a function Maybe
(in monadic terms, our unit function) that returns an object Nothing
if the value provided to it is null
or undefined
. Likewise, it returns a function Something
that returns the original value if the value is not null
or undefined
. (For clarity, I’ve renamed Haskell’s Just
with Something
, as I find this terminology a bit easier to follow when first being introduced to the concept.)
What can we do with the above? Let’s try it out:
Maybe(null) == Nothing; // true
typeof Maybe(null); // 'object'
Maybe('foo') == Nothing; // false
Maybe('foo')(); // 'foo'
typeof Maybe('foo'); // 'function'
So now we’ve put all of our !=
null checks into a single function, which is the constructor for the new Maybe
type. But is this enough to solve our problem? Unfortunately, no. Let’s try it out:
if (Maybe(person) != Nothing &&
Maybe(person["address"]) != Nothing)
{
var state = Maybe(person["address"]["state"]);
if (state != Nothing) {
console.log(state);
}
else {
console.log("State unknown");
}
}
So far, all we have done is replace a null check with a check for Nothing
. This is not quite what we want.
Maybe We Need Composition
One of the defining characteristics of a monad is that it may be combined with other monads of the same type. That is, we should be able to sequence monads together through composition. You may remember that function composition is the application of one function to the result of another. Mathematically, given two functions g
and f
, the composition of g
of f
is:
(g ∘ f)(x) = g(f(x))
In the case of Maybe, we need some way to take multiple Maybes and combine, chain or bind them together in a meaningful way. This way, if one Maybe is Nothing
we can short-circuit our computation and stop at Nothing
, otherwise we can continue on our way, essentially replicating what the &&
provides in our first example (technically, in JavaScript the computation does not short-circuit as it would in a lazy language like Haskell, but the effect is the same).
We can do this by introducing a method on Maybe called bind
(in Haskell, this is the >>=
operator) that makes specific use of function composition. This bind
method applies a function to the value contained by a Maybe and returns a new Maybe that contains the value of the function application. Since Nothing
has no value, anything bound to a Nothing
should simply return Nothing
(our short-circuit).
Maybe = function(value) {
var Nothing = {
bind: function(fn) { return this; }
};
var Something = function(value) {
return {
bind: function(fn) { return Maybe(fn.call(this, value)); }
};
};
if (typeof value === 'undefined' || value === null)
return Nothing;
return Something(value);
};
With this new bind
method we can more elegantly re-write our code:
var state = Maybe(person).bind(function(p) {
return p["address"];
}).bind(function(a) {
return a["state"];
});
if (state == Nothing) {
console.log("State unknown");
}
else {
console.log(state);
}
Certainly this is better than before, but can we do better?
(Note: If you’re keeping score, then you’ll note the type signature of our
bind
differs from Haskell’s>>=
. Haskell’s bind operator is of typem a -> (a -> m b) -> m b
, whereas ours ism a -> (a -> b) -> m b
. That is, we should pass in a functionfn
that returns a non-monadic – non-Maybe – value. I do this because JavaScript’s type system is, understatedly, quite weak, so I prefer to enforce the wrapping of the function’s return value in the Maybe monad myself. You can of course elect not to do this, and instead ensure that any function you pass tobind
always returns a Maybe.)
Maybe We Can Do Better
It would be nice if we could eliminate the final if ... else
statement in the example above. It would also be nice if we could sequence multiple Maybes together without the need for bind
in the case when we don’t plan on using the result of the bind. Fortunately, with our new Maybe type we can do all this and more. Here’s the final Maybe code with a few new methods (isNothing
, val
and maybe
) that provide some additional utility:
Maybe = function(value) {
var Nothing = {
bind: function(fn) {
return this;
},
isNothing: function() {
return true;
},
val: function() {
throw new Error("cannot call val() nothing");
},
maybe: function(def, fn) {
return def;
}
};
var Something = function(value) {
return {
bind: function(fn) {
return Maybe(fn.call(this, value));
},
isNothing: function() {
return false;
},
val: function() {
return value;
},
maybe: function(def, fn) {
return fn.call(this, value);
}
};
};
if (typeof value === 'undefined' || value === null)
return Nothing;
return Something(value);
};
isNothing() and val()
The isNothing
and val
functions are rather self-explanatory. The isNothing
function returns true
if the Maybe is Nothing
and false
otherwise. The val
function simply returns the value inside the Maybe monad if it is “something,” similar to Haskell’s fromJust
function. If the Maybe is Nothing
then val
will throw an error. We don’t require these methods for our example (or even for Maybe to be a monad), but they often prove useful elsewhere.
maybe(def, fn)
The maybe
function is the most useful for our purposes, and is identical to the maybe
function for Haskell’s Maybe monad. It takes a default value (def
) and a function fn
and if the Maybe is Nothing
, returns the default value, otherwise it applies the function to the contents of the Maybe and returns the result. We can use this handy function to rid ourselves of the final if ... else
statement in our example:
console.log(Maybe(person).bind(function(p) {
return p["address"];
}).bind(function(a) {
return a["state"];
}).maybe("State unknown", function(s) {
return s;
}));
And now we have our solution.
But is Maybe a Monad?
Thus far, I’ve been calling our Maybe implementation a monad without really proving it. Nevertheless, hopefully you now have at least a vague sense of what a monad is, even if I haven’t presented any kind of formal definition.
So, what is a monad? Perhaps the most intuitive way to think about monads is as chainable computations, or even “programmable semicolons.” They allow us to wrap up computations and sequence them in meaningful ways. In the case of the Maybe monad, the computations that we choose to wrap up are our != null
checks, and we sequence them through our chained use of bind
.
Of course, monads may also be defined more formally. For our Maybe example to truly be a monad it must have three particular properties and obey three particular laws. Of the three properties it must:
- Have a type constructor that defines its type.
- Have a unit function that converts a value of some type to its corresponding monadic type. This is the Maybe function.
- Have a binding operation that takes a monad, a function that takes a some type and returns a monad. This is our
bind
function (Again, note that in our example, the function type signature varies slightly from this definition, as we automatically wrap the result of our binding function in Maybe).
As for the three laws, these are known as: left identity, right identity, and associativity. In JavaScript, with our example, these laws may be written as follows:
Left identity
Maybe(x).bind(fn) == Maybe(fn(x)); // for all x, fn
Right identity
Maybe(x).bind(function(x){return x;}) == Maybe(x); // for all x
Associativity
Maybe(x).bind(fn).bind(gn) == Maybe(x).bind(function(x) {
return gn(fn(x));
}); // for all x, fn, gn
Feel free to try these laws out with a few examples to see that they hold true.
Redefining Nothing
We’re almost finished, but I want to take things one step further. Thanks to JavaScript readily allowing us to manipulate an object’s prototype, we can perform some additional tricks that make Maybe even more useful.
Consider the following alteration to our running example:
var person1 = {
"name":"Homer Simpson",
"address": {
"street":"123 Fake St.",
"city":"Springfield"
}
};
var people = [person1];
if (people != null && people.length > 0) {
console.log(people[0]);
}
It would be nice if we could wrap up our check for an empty array as part of our Maybe monad. Fortunately we can. First, we will “mix in” a new function called isNothing
on the prototype of Array:
Array.prototype.isNothing = function() {
return self.length == 0;
}
Next, we will extend the Maybe constructor to check for this function on all provided values:
Maybe = function(value) {
// Nothing and Something definitions go here ...
if (typeof value === 'undefined' ||
value === null ||
(typeof value.isNothing !== 'undefined' && value.isNothing()))
{
return Nothing;
}
return Something(value);
};
Now we can refactor our null
and empty array checks using bind as before:
console.log(Maybe(people).bind(function(people){return people[0]}).maybe("No person", function(person) {
return person;
}));
Using the same trick, we can change the definition of Nothing
for any object type we choose.
Conclusion
Hopefully this short introduction to Maybe and the world of monads has proven that the dreaded “M-word” need not be as intimidating as it sounds. Hopefully it has also shown that monads like Maybe can be quite useful, even in imperative languages like JavaScript.
Remember, a monad is really nothing more than a chainable computation. It is simply a functional way to sequence things. That’s it.
So, if you haven’t already, I encourage you to try the above Maybe examples for yourself, and perhaps even implement them in another language (I have an Objective-C implementation, for instance). Then, go forth and try making other monads using Maybe as a template. It’s not as hard as it sounds and the rewards may be some very, very useful code.
[1] Crockford also provides a lengthy description of JavaScript monads in a recorded talk at YUIConf, using Maybe as an example. However, I find his implementation using macroids more difficult to follow than the one I present here.
This article was originally posted at https://sean.voisen.org/blog/2013/10/intro-monads-maybe/
Image courtesy of https://upload.wikimedia.org/wikipedia/commons/5/5f/Monads.jpg
Crockford’s talk is incorrect in its definition of Monads, and you seem to have used that as the primary source of learning on the subject. If you look here: https://en.wikipedia.org/wiki/Monad_(functional_programming) you’ll find the formal definition. What you and Crockford think are monads are actually functors, transformations on data held inside a type of encapsulating structure. This is still a good thing, but you should remove your references to Monads and change it to Functors.
But then, monads are functors. Monads add chaining which is what makes them useful.
I’m not sure I follow the argument. Monads ARE functors, and in fact monads are applicative functors too.
Functors apply a function to an encapsulated value and return an encapsulated value (fmap :: (a->b) -> fa -> fb). Applicative functors apply an encapsulated function to an encapsulated value and return an encapsulated value ( :: f(a->b) -> f(a) -> f(b)). Monads apply a function that returns an encapsulated value to an encapsulated value (>>= :: fa -> (a -> fb) -> fb).
In this example, it is tempting to say that bind has the same type as fmap, so we don’t have a monad. (After all, it does apply a function to an encapsulated value and return an encapsulated value). But fmap doesn’t allow chaining, and in this example it couldn’t be defined as a method on Maybe to be implemented correctly. It would have to be a standalone function that takes two arguments. Essentially, by making fmap a method on Maybe, we have created >>=, since it now by default accepts a monad as its first argument (the instance on which it is a method). As such, we have more than just transformations on data; we have chainable transformations on data (aka a monad).
Also, F# computation expressions are not monads. They are usually used to provide syntax sugar for monads, but it’s merely convenient syntax sugar, not the monad itself. And not all computation expressions are about monads, you can do monoid sugar and other things with them.
Some examples doesn’t seem to work at all.
Considering the “then(maybe)” example:
If
person["address"]
is null, it fails completely the moment you try to passperson["address"]["state"]
: TypeError: Cannot read property ‘state’ of undefinedSame thing for your last example:
If
people
is actually null, you’ll get an error the moment you try to pass it to the first Maybe.The whole concept of using a function to verify whether a variable is defined doesn’t work in javascript.
Maybe monads really are cursed
You are correct about the “then” examples. JavaScript doesn’t support lazy evaluation like Haskell, so they do fail (every param passed to “then” will get evaluated no matter what). I will have to remove them. I added them last-minute, and that’s what I get for not thoroughly checking.
You can, however, use a function to verify whether a variable is of type undefined (making bind still useful), but not if it is never defined at all.
Nice write-up. There’s this though… you should do undefined checks this way:
typeof value === “undefined”
Also, this might be off-topic, but in javascript I’d prefer to do it this way:
maybe(person)
.has(“address”)
.has(“state”)
.then(function(state) {
console.log(state);
})
.else(function() {
console.log(“State unknown”);
});
Good point about typeof undefined. I missed that and will fix.
off-topic again: maybe(person).has(property).then().else() looks awesome
Do you have an implementation or any reference to to that?
Nice post, I find in JavaScript people tend not to realize monads is just a fancy term for method chaining, which the new promise apis are bringing int more prominence, and ALL of jquery uses. Also you should declare all your variables with `var`, and most of your functions look like they might be better written as `function Name(args){` not `Name = function(args){`.
Most awesome post, very impressed.
I’ve put this to immediate use in my coding for both client and server side JS.
Thank you!
Interesting pattern. I decided to create my own variation of this with CoffeeScript and wanted to share it: https://github.com/KarlPurk/maybe. I’m guessing what I’ve created isn’t a monad, but it seems useful nonetheless. Thanks for the post.
I always do:
var state = person&&person.address&&person.address.state
Monads can be more readable though.