Monads
A monad is defined by:
- the
return
operator - a type constructor
m
(see Types in Haskell) - the
>>=
operator, which is pronounced 'bind'
The >>=
operator has the following definition:
(>>=) :: m a -> (a -> m b) -> m b
The return
operator has the following definition:
return :: a -> m a
In the above, m
is a type constructor that takes one argument. Then, the Monad
class definition is:
class Monad m where (>>=) :: m a -> (a -> mb) -> mb return :: a -> m a
1. do notation
Calls to >>=
can be nested:
Just 3 >>= (\x -> Just 4 >>= (\y -> return x+y))
This can be re-written in do-notation as:
foo :: Maybe String foo = do x <- Just 3 y <- Just 4 return x+y
Note that on each line, >>=
is being used implictly
2. example: maybe monad
instance Monad Maybe where return x = Just x Nothing >>= f = Nothing Just x >>= f = f x
2.1. how to think about the Maybe
monad
What is the point of using monads here? The Maybe
provides a context for the computation. The computation, f
, that we want to do, only takes place if it's in the right context: namely the Just
context.
3. example: list monad
instance Monad [] where return x = [x] xs >>= f = concat (map f xs)
3.1. how to think about the []
monad
You can think of f
as having a non-deterministic output – for a single value, it produces many values. Here, f
is also being executed in a non-deterministic context. Note that f
doesn't know that it is being executed in a non-determinisitc context. All of that is handled by how >>=
is written.
3.2. example: IO monad
An IO monad contains a value that has been obtained via an IO operation. So,
getLine :: IO String putStrLn :: String -> IO ()
can be combined like:
getLine >>= (\x -> putStrLn x)
Here, x
is the 'unpacked' result of the IO getLine
, which can be thought of as a box that goes out into the world and fetches a value. Under the hood, the IO
monad also introduces data dependencies in such a way that IO operations can be sequenced (see this wiki link).
3.2.1. useful links
4. how should we think about monads?
Lots of interesting answers in this stack overflow post.
If we have functions:
f :: String -> Int g :: Int -> Float
It's pretty obvious how we should compose them using (.)
. But when we have:
f :: String -> Maybe Int g :: Int -> Maybe Float
how should we compose them? Monads specify how. It tells us that the composition of these two functions is the following:
\x -> f x >>= g
All the unpacking and re-packing has been handled by the join >>=
We can think of monads as tools for abstraction. Really, the code that we are interested in writing are the functions f
. We don't want our program cluttered up by all the code needed to handle composing these functions together. For []
, we abstract away the glue code that handles concatenation of the results. For Maybe
, we abstract away the glue code that handles termination if Nothing
occurs.
4.1. contrast to functors
For a functor, the definition of fmap
is:
fmap :: (a -> b) -> f a -> f b
Notice that the function (a -> b)
only has access to the value a
. It can't specify anything about the output's context. For example, applying fmap
to a Maybe
will never change a Just a
to a Nothing
. In contrast, monads allow us to apply functions of the type a -> M b
, in which this is possible.
Originally, monads were not a subclass of applicative functors, but they are now.
5. Monad laws
5.1. left associativity
return x >>= f
is equivalent to f x
.
I think of return x
as the minimal containing box for x
. Then, binding f
to that box will unpack the value and then apply f
. This should be the same thing as just applying f
to x
.
Or: return
is a left-identity with respect to >>=
.
5.2. right identity
m >>= return
is the same thing as m
. Note that here, m
is a single concrete type, such as Just String
.
So, unpacking something from a monadic wrapper and then re-packing it in the minimal containing wrapper will result in the same thing as constructing the monad directly.
5.3. associativity
(m >>= f) >>= g
is the same as
m >>= (\x -> f x >>= g)
The first one is read as: apply f
to m
to get a new monadic value, and then apply g
to that new value.
The second one is read as: apply to m
, the function that unpacks x
, applies f
to x
to get a new monadic value and then applies g
to the new value.
6. Useful functions
6.1. replicateM
Applicative m => Int -> m a -> m [a]
Then, replicateM 5 as
is equivalent to:
do a1 <- as a2 <- as a3 <- as a4 <- as a5 <- as pure [a1,a2,a3,a4,a5]
6.2. sequence
The definition (from here):
sequence :: Monad m => [m a] -> m [a] sequence = foldr mcons (return []) where mcons p q = p >>= \x -> q >>= \y -> return (x:y)
sequence
takes a list of monadic computations and returns a list of their results, wrapped in a monad. Note that if using the IO monad, using >>=
will cause the IO action to run.