A Trip To The Magical Land Of Monads
Monads can be a tricky topic. On the surface, they’re not hard at all. Taking
Maybe for instance:
Prelude> Just 1 Just 1 Prelude> Nothing Nothing
That couldn’t possibly be easier. Something is either
Just [something], or it is
Nothing. However Monad world is the magical world of gotchas, unenforceable rules, and magical syntax. I had been planning on writing a post on Monads, much like I did with Applicatives and Functors. While researching this topic, I’ve determined that I’m not qualified to speak on this subject yet.
I am qualified to discuss the usage of Monads. I feel that say you are going to “learn how to do Monads” is similar to saying you are going to “learn how to do data structures”. Data structures are similar to each other in that they serve a common purpose: to contain data. However, a Vector is nothing like a Linked List, which is nothing like a Hash Map.
Maybe is nothing like an
IO, which is nothing like a
Writer. While they are all Monads, they serve different purposes. Today, I’d like to lay some groundwork on the topic and talk about binding functions.
On Magical Syntax
Monad brings functions that allow you to use normal values with monadic values.
Monad brings the
>>= operator. This operator is called the bind operator. (this is important for monads in general, but I won’t get into this today. Just know that the operator’s name is bind) The signature for
(>>=) :: m a -> (a -> m b) -> m b
As you can see, it takes a
Monad that contains an
a, a function that takes an
a and returns a
Monad b, and returns a
Monad b. Basically, it calls the function on the value contained in the monad, and does whatever additional action is appropriate for the monad, and returns a monad containing the result. In short: it is
Monads. Let’s take a look at a quick example for
Prelude> Just 1 >>= (\a -> Just $ a + 5) Just 6
As you can see, we bind
Just 1 to a function that takes a value, adds it to 5, and wraps the result in a
Just, which results in
Just 6. Bind will correctly handle
Nothing as well:
Prelude> Nothing >>= (\a -> Just $ a + 5) Nothing
Still with me? Good, because things are about to get magical.
Monads are so special, they have their own magical syntax! When working with monads, you may use
do notation. What does do notation look like? Let’s take a look at a sample function:
justAdd :: (Num a) => a -> a -> Maybe a justAdd a b = Just $ a + b
This function takes 2 numbers, adds them, and wraps them up in a
Maybe. Nothing earth shattering here. Let’s take a look at how to work with these using Bind:
justAddDemoBind = justAdd 2 2 >>= (justAdd 4) justAddDemoBindNothing = Nothing >>= (justAdd 4)
I’ve defined 2 simple functions here. The first calls
justAdd 2 2, which returns
Just 4. The function
(justAdd 4) is then applied to it using the bind operator, which will return
Just 8. The second attempts to apply the function
(justAdd 4) to
Nothing. Since the bind operator is smart enough to handle this, the final result of this function is
Nothing. Simple, really. Now, let’s see
do notation in action:
justAddDemoDo = do first <- justAdd 2 2 justAdd 4 first justAddDemoDoNothing = do first <- Nothing justAdd 4 first
Looks like a completely different language, right? In fact, these two functions do the exact same thing as the previous two. The purpose of
do notation is to make working with Monads easier. In practice, if your logic is non-trivial, you end up with hugely nested statements. To see what’s going on, let’s break
justAddDemoDo = do
In the first line, we open our
first <- justAdd 2 2
In the second line, we call
justAdd 2 2, and assign the result to the name
first. Notice that
<- operator? That works basically the same as it does in list comprehensions, it does the assigning.
justAdd 4 first
Finally, we add 4 to first, resulting in
Just 8, which is the value returned from the function. It seems like we’re treating our monad contained in
first as a regular value. This is part of the magic of
do notation. In fact, if this line were written as:
justAdd first 4 it would have worked.
Another very important thing to note is that the last line of a do block must be an expression that returns a Monad! GHCI will throw a fit if it’s not.
The Pervasiveness Of Do
As you can see, under the hood,
do notation just uses the
>>= operator. You can also see that it is much simpler and cleaner looking than using the bind operator. However, that doesn’t mean that
do is better. Like many things, it comes down to personal choice.
When reading literature on Haskell, you should be prepared to interpret
do blocks, and usage of the bind operator. Like any tool,
do and bind are not always the correct one. Picking the correct tool for the job is part of being a programmer. Hopefully this tutorial gave you enough familiarity to be able to use both.