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).
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.