A gentle intro to monads … maybe?
October 17, 2013
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.
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:
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:
All this means is that an object of type Maybe either has some value (
Just t) or no value (
Nothing). What is meant by
undefined. But as you will see, with the Maybe monad, we can change the semantics of “nothing” to suit our needs.
Now we have a function
Maybe (in monadic terms, our unit function) that returns an object
Nothing if the value provided to it is
undefined and returns a function
Something that returns the original value if the value is not
undefined. (For clarity, I’ve renamed Haskell’s
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:
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:
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
f, the composition of
(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
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).
With this new
bind method we can more elegantly re-write our code:
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
binddiffers 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
bindalways 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 (
maybe) that provide some additional utility:
isNothing() and val()
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
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 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:
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
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
- 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
bindfunction. (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.)
Feel free to try these laws out with a few examples to see that they hold true.
prototype, we can perform some additional tricks that make Maybe even more useful.
Consider the following alteration to our running example:
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
Next, we will extend the Maybe constructor to check for this function on all provided values:
Now we can refactor our
null and empty array checks using
bind as before:
Using the same trick, we can change the definition of
Nothing for any object type we choose.
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.