A Monad Non-Tutorial
24 January 2020
“What is a monad?” is one of the most common question when you’re learning Haskell. And it’s there when troubles start, because it is the wrong question. Ask a wrong question, and you’ll get the wrong answer. The only right answer to this question is a mathematic definition:
[…] A monad is a triple
(M, unitM, bindM)
consisting in a type constructorM
and a pair of polymorphic functionsunitM :: a -> M a
andbindM :: M a -> (a -> M b) -> M b
These functions must satisfy three laws […]
1
2
3
4
(unitM a) `bindM` k = k a
m `bindM` unitM = m
m `bindM` (\a -> (k a) `bindM` (\b -> h b)) =
(m `bindM` \a -> (k a)) `bindM` (\b -> h b)
After a reverential minute of silence, you start to figure it out that maybe you shouldn’t be asking anyways. Your mind is working fast to come out with a phrase to fool your interlocutor into thinking that you have understood. But there’s nothing to understand.
You’ll see, that’s the way definitions work. They are used to label objects, that’s the only thing they are good for. So if the definition of homo sapiens is “an animal, mammal, of the order of primates, family hominidae and genus homo” and if you know what is an animal, a mammal, a primate… etc. then you can put objects into classes: “my friend Ed, homo sapiens; mi dog Buch, not.”
It is why, not what
The least interesting thing about monads is its definition. The real question is “why?” You only make a definition if it is useful, if you use it frequently. So, let’s see how this definition is implemented in Haskell:
1
2
3
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return a :: m a
So, a monad is an Haskell class. Or, if you are a Java guy (and let’s confess
it, who isn’t?), then you can think in a monad as an interface. But, again,
why? When you see FileishInterface
with its methods open, close, read, write
and seek, you say: “Oh, so FileishInterfac
e represents objects that behave like
a file, I get it”. You can image an instance of FileishInterface
using a TCP
connection or a byte array. But a monad doesn’t provide to the
untrained eye any clue of what id does.
And that is a good thing. After all, FileishInterface
is so concrete that it
could be used only in very few cases. The interface of Monad is so generic
that it could be used in a wider range of sitations. Paradoxically, that makes
it hard to think in a use when you are learning the concept.
When
The other problem when you are learning monad is that you want to use them. You open your editor and say “I’ll make a program and I’m gonna create my own monads and I’ll show them in the Haskell Café and I’ll become a member of the Secret Category Cabal and I’m gonna spend my evenings talking about Kleisi arrows and functors and maybe Paul Hudak will friend me in facebook”. Curiously, you have never say “Mmm, today I feel like I’m gonna make a program that uses hashtables”. You don’t try to impose data structures to your imperative programming, so why are you trying to force monads in haskell? Don’t go after monads, let monads find you.
You’ll know when you need them. One day you will make a program and you’ll write a type constructor:
1
type M a = ...
(did you notice how your type constructor’s name is “M” as in “Monad”? This is a clever, subtle way in which the author – me – hints that you’ve started to write a monad).
If you use your type constructor M
to create only one type, say M Integer
, then
you don’t need monads. But, after a few hours of programming, types start to
appear and you have M Char
, M String
, M Bool
and others. After a few coups of
coffee you have functions all over the place with types
like Integer -> M Integer
, Integer -> M Bool
, Bool -> M Char
.
Then you need to chain these function together, but it’s a pain in the
functional ass. Each time you need to connect two functions, say
Integer -> M Bool
and Bool -> M Char
, you need to invoke the first function,
extract the value inside M
and invoke the second one. And it is then –I hope–
that the “aha! moment” comes to you. You can write a function that extract the
a
from M a
and use it to invoke a function with type a -> M b
. You have
invented >>=
, the bind function!
Since you wrote the type constructor M
, you are the one who knows how to extract
a
from M a
. And you know how to chain that value to a -> M b
. Each monad has
its own definition of >>=
, adapted to its structure.
When is return used? If you see the >>=
definition, it needs a M a
to start. If
you have a value of type a
instead, you’ll need a way to transform it into
M a
. That’s what return does.
Now what?
Hopefully you know by now that the definition of monads is not the important
part for a programmer. You only need to focus in the type constructor and the
bind function. What does the type constructor represent? What is it used for?
How do they defined bind? How does it extract the value inside one of those new
types and how does it chain it to the a -> m b
function?
So, now you can go back to those tutorials that explain what are monads and give
them a second look. You can review the do notation that help you to write your
long chain of >>=
expression in a clear way. Good luck!
The IO Monad
But wait! I can’t end this post without talking about IO monads, can I? A monad is a monad is a monad. IO monads are not the exception. Since your understanding of monad is better now and my hands are tired, I’ll cheat a little.
An IO a
value can be seen as a normal imperative program that, when executed,
returns a value of type a
. For the IO
monad, the bind operator is used to
compose those “programs” with functions that return “programs” into new, more
elaborated ones. That’s why the haskellers can see you right into the eye
and say that Haskell is a pure language: “Of course that putChar is a pure
function. Giving the same input, it will always return the same C program!”.
Thinking in this way is useful to understand why IO is a monad, but it is a waste
of your attention. In your daily programming, you’ll be better thinking that the
IO values are an imperative language embedded in haskell and >>=
is its interpreter.