A Gentle Intro to Monads … Maybe?

October 17, 2013

Monads: they're incredibly useful, and also maybe 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 or 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 and 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 type m a -> (a -> m b) -> m b, whereas ours is m a -> (a -> b) -> m b. That is, we should pass in a function fn 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 to bind 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 final 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, 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.