Monads In Clinical Terms
Last week, we briefly discussed Monads. If you haven’t read that post, it would probably be helpful to do so now. To recap, I talked about two things. First and foremost I talked about how monads are horrific and terrifying. Secondly I talked about the use of the bind operator, and
do notation in Haskell.
Today I’ll be discussing at a very low level, what exactly a Monad is. I feel that there is a general notion that all Monads are similar to each other, and serve some common purpose. I know this has been causing issues for me. However, I’m slowly coming around to the idea that they really aren’t. Let’s consider some of the monads in Haskell. We have:
Listmonad, which is a data structure.
Maybemonad, which represents the result of a calculation that may have failed.
Eithermonad, which represents a value, or an error.
I know what you may be thinking: “These all look like data structures!” I had this same thought, but let’s look at some more monads:
IOmonad, which represents some communication with the “outside world”.
Writermonad, which represents logging.
Statemonad, which represents program state.
…and then that notion falls apart. If you tried really hard, you might make a case that these are all data structures. However, they’re really not. So, what exactly is a monad? At the very basic level, a monad is two functions, and three laws. Let’s take a look at these.
I touched on this a bit last week, but in order for something to be a monad, it must implement two functions:
return. Let’s take a look at these.
Any monad typically contains a value, and some “decoration”. Bind takes a monad, a function that takes a value and returns a monad, calls that function on the value contained in the old monad, returning a new monad. Finally the logic contained within the bind function “reconciles” the old monad and new monad’s decorations, returning the final resulting monad. Conceptually, this looks something like this:
Let’s take a look at Haskell’s bind operator’s function signature:
(>>=) :: m a -> (a -> m b) -> m b
In Haskell, bind is implemented as an operator:
>>=. This takes a monad containing an “
a“, a function that takes an “
a“, and returns a monad containing a “
b“, and finally returns a monad containing a “
b“. It is up to the author of a monad to determine what it means to “reconcile the decoration” taking into account the laws I’m going to talk about in a minute.
Compared to bind,
return is child’s play. Simply put,
return takes a value, and creates a new monad with the most basic amount of decoration to still be valid. This is functionally equivalent to the
pure function provided by Applicative Functors.
While you can make any old thing a member of the
Monad typeclass, it’s not actually a monad unless it obeys the law. There are three properties that a monad must satisfy in order to be a valid monad:
Formally defined as:
return a >>= k == k a
This law states that calling
return on some value, and then binding the resulting monad to some function should have the same result as just calling that function on the value. The idea here is that if
return truly gives a value minimal decoration, that this decoration should not change the resulting monad of a function when bound. Let’s see how
Maybe satisfies this law. Here is
Maybe‘s instance definition:
instance Monad Maybe where (Just x) >>= k = k x Nothing >>= _ = Nothing return k = Just k
return implementation is called, it wraps the argument in a
Maybe has two bind implementations, one for
Just and one for
Nothing. If a
Just is bound to a function, the function will be called on the value contained in the
Just, and the resulting
Maybe is the result of the bind function. Therefore, if the function returns a
Just, bind returns that
Just. If the function returns
Nothing, then bind returns that
As you can see,
return always returns
Just k, therefore
Maybe satisfies the law of Left Identity.
Formally defined as:
m >>= return == m
return is a function that takes a value, and returns a monad. However,
return is only supposed to just wrap the value in a minimal monad. Therefore, the monad created by
return should have no affect on the “decoration reconciliation” performed by bind. Let’s see how this works with
As you can see from the implementation posted above,
Maybe has two bind implementations: one for
Just, and one for
Nothing is bound to any function, the result is automatically
Just k is bound to a function, the result is the result of the function call. The result of
return function is to wrap the value in a
Just. Therefore, for both bind implementations, the law holds.
Formally defined as:
m >>= (\x -> k x >>= h) == (m >>= k) >>= h
At first this one seems complicated, but it’s really not. First, we need to understand what the word “Associativity” Means. In math, the Associative Property is a property of certain binary operations. If an operation is associative, then it does not matter how the numbers are nested, the result will be the same.
Addition is associative:
(2 + 3) + 4 = 9 2 + (3 + 4) = 9
…however subtraction is not associative:
(2 - 3) - 4 = -5 2 - (3 - 4) = 3
Simply put, bind must behave like addition, and not subtraction with regards to how it can be nested.
When You Put It Like That…
Doesn’t seem so complicated now, does it? Did all that make sense to you? Congratulations, you get monads! Now, the hard part is figuring out how to use some of the crazy monads that come in Haskell’s standard library. This is a subject I hope to help with over the coming months.
However, for the time being if you have a data type, and you can implement bind and
return in a way that obeys the monad laws, then your type is a monad. Don’t let anybody tell you otherwise!