A Gentle Introduction to Monads in JavaScript

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 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 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

Previous

Using Horizontal Design for Websites

Building a Blog with Jekyll

Next

14 thoughts on “A Gentle Introduction to Monads in JavaScript”

  1. 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.

    • 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).

  2. 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.

  3. 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 pass person["address"]["state"]: TypeError: Cannot read property ‘state’ of undefined

    Same 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.

  4. 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”);
    });

  5. 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){`.

Comments are closed.