Just like the way a Jedi must make his own lightsaber, it’s a rule that, if you’ve heard of Haskell, you must write own monad tutorial.
A given monad m has two operations defined:
(>>=) :: m a -> (a -> m b) -> m b return :: a -> m a
And they obey the following laws:
return a >>= f = f a m >>= return = m (m >>= f) >>= g = m >>= (\x -> f x >>= g)
There! Internalize that, and then you’ll understand monads.
The End.
Epilogue
We’re done. There’s nothing else to say. Go internalize that. What do you want, some kind of crazy metaphor? Do you think if only we explained monads in just the right way, in just the right combination of words and analogies, it would tingle your neurons in just the right way so that it would be easy to understand? What’s so hard about internalizing things, anyway? You just have to take those laws and turn them into some mental hairball.
Let’s digress.
Have you internalized the commutative and associative laws of addition? You know, these laws:
a + b = b + a (a + b) + c = a + (b + c)
Somehow, you’re very comfortable with addition. You know that you can take a sum and reorder it any way you want. Why can you do that? It’s because of the associative and commutative laws. When you go and add something to a sum, you’re just throwing that number into the unordered goop with the rest of them. The same goes for multiplication of real numbers. Multiply real numbers, and you’re just putting together a big bag of factors. But when multiplying matrices, you don’t have the commutative law. It’s a different kind of goop — instead of being a bag of factors, it’s more like a line of factors — you can multiply stuff onto the sides, and you can cut up the line however you want, but you can’t rearrange the parts.
You’re used to that idea. You’re used to the noncommutativity of string concatenation and matrix multiplication. You’re used to the commutativity of real number addition and multiplication. You have a mental model for how these things feel. And that’s what you want for monads.
How are you going to get that? How can you translate the monad laws into mental goop that you understand?
Maybe you can look at enough examples and then get used to monads. One day, you’ll realize that you understand them. That’s how it often works. Another way is to forget about the general law and just think of a specific example. IO is the famous monad, so let’s go with that.
What does (>>=) mean when you’re using the IO monad? I’ll make it easy by telling you:
action >>= continuation
That’s what it means. It attaches an action (something that produces a return value) to a continuation (something that takes a return value [of some other action] and does stuff with it). “Continuation” is just a fancy name for a function.
For example:
getLine >> (\x -> putStrLn ("Your string is " ++ x))
getLine is an action of type IO String — it’s an action that returns a string. We attach that to a continuation, of type (String -> IO ()), that takes the return value of something and performs another action. Combining them together, we produce a bigger action.
(>>=) :: IO String -> (String -> IO ()) -> IO ()
-- ^action^ >>= ^continuation...^ = ^result^
If you want to glue together actions declaratively, that’s how you have to do it: by gluing together actions with continuations that use the return value of said action, producing larger actions. The function return just produces a do-nothing action that returns a value. So if we were to write
return "Hello, world" >>= putStrLn
what kind of action are we making? We’re taking a do-nothing action that returns a String, and attaching it to a continuation that takes a string and prints it out. It’s the same as if we wrote
putStrLn "Hello, world"
Making a general rule, we can say
return x >>= f = f x
Constructing an action by attaching a do-nothing action to the continuation f is the same as constructing the action directly through the function f.
And that explains why IO obeys the first monad law. What about the second? The second monad law says:
m >>= return = m
Okay. Then we’re taking the return value of the action m and sending it through a do-nothing continuation. (We can rewrite that rule as the following:)
m >>= (\x -> return x) = m
As long as our do-nothing continuation passes the return value along, there’s no difference in behavior.
So the first two laws say that return does nothing. So let’s look at the third monad law and see how that makes sense.
(m >>= f) >>= g = m >>= (\x -> f x >>= g)
This law, called the “associativity” law, says that it doesn’t matter how we cut up our actions into smaller actions. Order matters — but how we cut up the actions doesn’t. To rephrase: this third law says that attaching a continuation, and then another continuation, is the same as combining the two continuations into a larger continuation and then attaching that.
So that explains how IO is a monad. But there are other monads that you need to look at yourself.
If any of this is hard to internalize, that’s not surprising. It was hard for me. Writing some Haskell code, and then sleeping, helps the learning process a lot. You’ll just get used to them. Later, you might feel like you understand IO just fine, but you’ll feel clueless when somebody writes replicateM 8 "01" and you don’t understand how that works because you haven’t internalized the list monad. Well, then you’ll have to internalize that, too.
Monads are convenient because once you internalize how the associative law makes sense in that particular kind of monad, it’s relatively easy to comprehend the meaning of the various monad-based higher order functions like replicateM, foldM, and such. It’s a good thing, because you can reuse the same names for the same kind of behavior. The general Monad type class exists for that reason — if we wanted, we could make a version of Haskell without the Monad type class, where we define (>>=) and return to specifically work with IO and nothing else. That wouldn’t be as useful.
So. That’s the end of this monad tutorial. Good luck learning and using Haskell. It has its moments.