Why Monads?
Why another Monad tutorial?
It has taken me a long time to learn Haskell. I would guess that I didn’t really understand Monads until using Haskell for over a year, despite reading a few tutorials about them and working with them every day.
A lot of my difficulty was more in choice of Monads than in Monads themselves. But it was a fundamental misunderstanding of the basic concept of Monads that was blocking me from being proficient in Haskell.
Eventually, my neurons did grow into the shape of a Monad. This essay is the explanation that makes sense to me and serves me practically as I write and read Haskell code. My ideas may, in fact, be wrong, because I am no expert. But if I am wrong, I’d like to know, so please tell me.
Haskell Needs Monads
Monads are not just some fancy mathematical concept borrowed by Haskell to make it sound cool and complicated. Haskell needs Monads.
Haskell is purely functional and lazy. A program without Monads has no guarantees about what order the steps of the program will be executed in. It’s fine for purely functional programs. But once you want to do Input/Output, you need guaranteed ordering.
For instance, the classic case is prompting a user, then reading input:
Please enter your username:
>You must do those things in that order, otherwise, your user gets the prompt after he enters input. That doesn’t make sense. If you think about it, all IO operations must have a guaranteed order. We take this for granted in languages that are not lazy.
Monads are a way to guarantee the order of operations in a purely functional, lazy language. Monad, in fact, is just a type class, defined like any other type class. There’s nothing special about them, as far as the language is concerned.
You can even use Monads without IO to guarantee an order. For instance, Maybe is a non-IO Monad. Separating sequencing from IO is one of the elegant things about Haskell.
Monad basics
Let’s look at what ghci tells us about Monad.
Prelude> :info Monad class Monad m where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a fail :: String -> m a
Monad defines four methods.
return
return just tells us how to make a Monad from a value. It is not the same as return in most languages, which short-circuits out of a function. By applying return to a value, you have made a Monad.
fail
fail is not part of the technical, mathematical definition of a Monad. In fact, you can leave it out of any Monads you define because it has a default definition. It is called when a pattern match fails and stops any further processing of the Monad.
>>= and >>
These operations are the meat of the Monad. These are the operators which define how to impose an order on the operations.
a >> b means perform operation a, then perform operation b.
a >>= b means perform operation a, then perform operation b on the result of a.
That’s it. Some people even call >>= and >> “then”.
A trivial example
You get sequencing of operations by defining these methods. And Haskell is nice and only requires you to define >>=, because the >> is trivially defined in terms of >>=. A typical definition of a trivial Monad follows:
data MyMonad a = MyMonad a instance Monad MyMonad where return = MyMonad (MyMonad a) >>= f = f a
I say this is trivial because this Monad isn’t very interesting. But it does guarantee an order. For instance, I can write:
MyMonad 1 >>= MyMonad . (+) 2 >>= MyMonad . (*) 10
This will return MyMonad 30, because the order of operations is guaranteed.
Maybe as a Monad
I mentioned before that Maybe is a Monad. What does ordering of operations have to do with Maybe?
Let’s look at how Maybe implements Monad:
instance Monad Maybe where return x = Just x Nothing >>= _ = Nothing (Just x) >>= f = f x
The definition for >>= ignores the second argument when the first argument is Nothing, but applies the second argument when the first argument is Just x. What does this do?
Let’s read it in plain English: “If the first argument is Nothing, then Nothing. If the first argument is Just x, then apply f to x.”; When you have Nothing, no further operations happen.
This technique is commonly referred to as “short-circuiting”. I’ll talk a little more about short-circuiting later. How would you use this short-circuiting of Maybe? Here’s a silly example.
non0OrNothing :: Fractional a => a -> Maybe a non0OrNothing 0 = Nothing non0OrNothing a = Just a safeDivide :: Fractional a => a -> a -> Maybe a safeDivide n d = non0OrNothing d >>= return . (n /)
safeDivide guarantees that the last line only gets called if d is not zero.
How does that guarantee the order?
The neat thing is that Haskell is still pure and lazy. Monads are never guaranteed to be created, nor reduced to a value. But when they are, you get guaranteed ordering. How does that happen?
Let’s expand our examples out. If we replace >>= with its definition:
MyMonad 1 >>= MyMonad . (+) 2 >>= MyMonad . (*) 10
becomes
(MyMonad . (+) 2) 1 >>= MyMonad . (*) 10
which becomes
(MyMonad . (*) 10) 3
which becomes
MyMonad 30The result of the first gets passed to the second, whose result gets passed to the third. In other words, they happen in order.
safeDivide 10 0
becomes
non0OrNothing 0 >>= return . (10 /)
which becomes
Nothing >>= return . (10 /)
which becomes
Nothing safeDivide 10 5
becomes
non0OrNothing 5 >>= return . (10 /)
which becomes
Just 5 >>= return . (10 /)
which becomes
(return . (10 /)) 5
which becomes
Just 2.0Again, we are guaranteeing the order from left to right.
That’s enough of the silly examples. Let’s see something more practical.
Other uses for Monads
Some smart people began discovering that once you have a guaranteed ordering, a lot of other stuff starts to fall into place.
State
What is state change but dividing time into before and after the change? Since we have a way to guarantee an order, we can now guarantee that some operations happen after the state is changed and some happen before.
That’s what the Monad called State does. It allows an operation to view the effects of previous operations on a shared bit of state.
Short-Circuiting
We saw before how Maybe short-circuits computations by ignoring the second argument to >>=. This is a common way to throw an error that aborts the rest of the computation (it aborts everything after the short-circuiting). Or it can be used to signal early success, where further computation is not always needed.
IO
Now we come to the topic of IO. I’ve already talked about how IO is not necessary for ordering. But ordering is necessary for IO.
The Haskell compiler has blessed one Monad (called IO) to pierce the purely functional veil that Haskell uses to cover the stateful von Neumann machines we all use. But it’s done in a type-safe way. You can only communicate with the outside world in the IO type. That’s all I’ll say about that.
Type System
Monads fit into the Haskell type system to give you a bit of type safety. You can define different Monads for different tasks. Stuff defined for one Monad will not work in another. The compiler won’t let you.
This was a big headache for me at first. At first I didn’t understand why I could not write the same code in two different places and expect the same results. Once I realized that it was a type issue, it then appeared to be a productivity killer.
Why was this type system getting in my way? I eventually learned to navigate the type system, but it wasn’t easy. My advice is simple:
Types are like barricades. They block your entry into wherever you put them. You can put them in front of lion cages and save people’s lives. Or you can put them in front of the supermarkets and prevent people from buying food. They’ll starve!
But if you put the barricades in the right places (that is, if you define your type system to model the problem well), then those barricades are a real help. Just know the types of your Monads and what you can and can’t do with them before writing lots of code.
do notation
do notation is syntactic sugar to write programs with Monads that looks more like procedural programs than does putting >>= between all of your expressions.
It lets you name the values produced at each step and use those names to refer to the values later. But behind the scenes, >> and >>= are being used.
Conclusions
That’s Monads in a nutshell. They’re not hard. But they are different. They’re actually a very simple concept but they pack a lot of power. It took me a while to learn them, but I appreciate them now. They let you guarantee an order to actions in a purely functional, lazy, and type-safe way. By taking advantage of the ordering property, they let you build in state and error handling into your otherwise stateless and functional program.
[...] http://labs.scrive.com/2011/12/why-monads/ [...]