I think becoming familiar with the mechanics of working with monads is more important than the question of what a monad is. For this reason, I think monad tutorials solve the wrong problem. I’m giving you a monad anti-tutorial instead, where I don’t try to explain what a monad is but I do try to show you how to use them.
I’m assuming basic familiarity with Haskell’s syntax, but even if you’re not I hope you’ll still be able to follow this.
Let’s start with a definition.
Monad in Haskell is a typeclass, which is very much like an interface in many other languages. Because Haskell is not object-oriented, it has types conform to typeclasses instead of objects conforming to interfaces, and each type can declare that it implements a particular typeclass by supplying definitions for each function that the typeclass specifies.
Monad looks very much like this:
This means that for any type
m, if you provide two function definitions called
(>>=) (pronounced ‘bind’) with the correct type signatures, you’ve made a
There are two things I think are confusing about this definition:
returnis a regular function whose meaning is unrelated to the
returnstatement in most other programming languages.
Haskell allows you to define infix operators by surrounding them in parentheses, and this one looks a bit odd and vaguely threatening with its spiky edges.
There are some laws that well-behaved implementations are supposed to observe:
I include these because they’re an important part of what a
Monad is. These laws aren’t enforced by the typeclass definition so it is possible to define unlawful instances. That’s a very bad idea though.
And that’s all. Really.
So what’s all the hype about?
One reason might be that Haskell has syntax sugar for this particular typeclass. This syntax sugar is known as
do-notation, and it allows you to write code that looks like
that then gets rewritten to
If you want to define variables in between you can use
and this becomes
Sometimes you don’t care about the variable on the left hand side of the
<-, and you can omit it like
which desugars to
I’ll be using the above definitions in my examples, so if you find yourself wondering where
bar came from a bit later, check back here.
I think it’s worth spending time on understanding
do notation and converting between the sugared and desugared representations because:
donotation will allow you to effectively use monads, whether or not you feel you understand them.
Doing this will teach you (or at least help you learn) the monad laws.
But enough about that. Let’s walk through some contrived examples to see how we can use the same typeclass (and the same functions) to do a bunch of very different things!
I’d like to start with the
Maybe type, which allows us to work with possibly
null values in a way I think is better than most other approaches. The implementation (or
Monad looks like
and in use it looks like
This captures the idea that if any of the parameters is
Nothing, the overall result should also be
Nothing. The cool part is that we don’t need to worry about this when defining
foo, because the underlying implementation takes care of it for us.
In a language like Python, we don’t really have a way to abstract away the fact that an input can be possibly null, and we have to specifically account for this possibility. Similar code in Python would look like
and this code would not work without modification for any of the following examples.
A second example is the
List type. A valid instance looks like
and you would use this like
Maybe can be one of two possible values, a
List is an arbitrary number of values.
In Python this would be a list comprehension:
which, again, is too specific to work with any of the other examples.
Maybe type could be thought of as a box containing either one or no elements, and similarly
List may be thought of as a box containing an arbitrary number of elements. It may be tempting to think of all monads as boxes of some kind, but monads are more general than that. Let’s look at the
Reader type, which allows us to
ask for some value that can be passed in later (in some circles this is called dependency injection):
I had to look this one up. I don’t expect you to immediately understand this implementation, my point is that this is bog-standard Haskell code. It’s a bit more interesting to use:
I have no idea how you’d do this in Python, but I can guarantee it’s not general either.
Finally, let’s look at the
IO type, which I find a bit scary. I don’t really understand how it’s implemented. so I’ll be skipping that section. Fortunately, we know how to use it, because we are familiar with the interface, so let’s go straight to that.
Okay, enough examples. As you can see, we’ve used the same interface to deal with failure, an arbitrary number of values, an extra parameter, and input/output. There are many useful monad instances in Haskell, from container types such as Seq and Tree to abstractions such as Writer and State, through to control flow as implemented by the Cont monad.
If you can make your type conform to the typeclass, Haskell will give you a pretty general and flexible API to work with it. Even if you don’t completely understand what’s going on with a certain type, you know enough to use it based on your knowledge of the interface. As far as I’m concerned, this is the point of monads in Haskell.