July 11, 2023
Understanding Monads - 2023
This is not another monad tutorial.
It is my own list of important things to know, when you
are thinking about Monads.
First think about "map"
If you have a basic understanding of functional programming,
you understand map. You have a function and a list and map
produces a new list with the function applied to every member
of the original list. Easy.
Generalize this to fmap
Rather than a list, consider some general sort of "container"
with something "in it". What fmap does is to apply a function
to such a containerized thing and return the result of the
function in the same container. For example the container
could be a Maybe. We can apply fmap to the Maybe because
Maybe is a functor. A functor is simply a type-class that
defines the fmap function.
Don't let these fancy names like "functor" scare you.
This is pretty basic and simple.
There is an infix operator for fmap that is (<$>).
If you understand map, understanding fmap just involves generalizing
the idea of a list as a container to some other (any other) kind of container.
Monads in their most basic form
A type that is a Monad (i.e. implements the Monad type class)
is obliged to define the following two functions.
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
Another function (>>) is also defined for a Monad
but for now it is worth just considering these two.
"Return" is famous, mostly because the name is so misleading.
It is nothing whatsoever like return in any other language.
People have suggested that "inject" might be better.
What is does is simple, it takes its argument and makes it a monad
of whatever type we are dealing with.
The other function (>>=) is often called "bind".
Understanding it strikes at the heart of what Monads are all about,
or how they can be useful (or used at the very least).
Keep reading.
To understand (>>=) and (>>) consider this:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
And consider this definition.
m >> k = m >>= \_ -> k
Leaving aside (>>) for now, let's look a "bind".
The first argument is "m a" (something in a container).
The second argument is "(a -> m b)" which is a function
that takes "a", does something to it, and puts it in a container.
So bind could be thought of like this.
We have something in a container.
Bind removes it, applies the function to it, and rewraps it.
Simple as that.
So what about (>>). It is just a handy "convenience" function.
You use it when you are binding once monadic computation to another,
and the second one does not require the result of the first one.
Here is the legal definition:
(>>) :: m a -> m b -> m b
m >> k = m >>= (\_ -> k)
What about do notation
You will see this explained in all kinds of ways that don't really give full understanding.
What "do" notation is, is a special mini-language (a DSL, i.e. domain specific language)
for dealing with monads. In particular, it hides the use of (>>=) and (>>) internally.
Feedback? Questions?
Drop me a line!
Tom's Computer Info / [email protected]