Monads
Monads are a way to describe effectful computation in a functional setting. In Haskell, they are unavoidable as they are used to support true side effects (writing to files etc) with the built-in IO monad. However, monads can also be used as a powerful program structuring technique, even for programs that do not directly interact with the outside world.
Applicative Functors
To motivate the value of monads (and some of the supporting
machinery), consider if we have a value x :: Maybe Int
. We can see
such a value as a computation that is either an Int
or Nothing
,
with the latter case representing some kind of failure. We can
interpret failure as a kind of effect, that is separate from the
functional value (Int
), although of course they are all just normal
Haskell types.
We are often in a situation where we want to perform a computation on the result (if it exists), or otherwise propagate the failure. We can do this with explicit pattern matching:
case x of
Nothing -> Nothing
Just x' -> Just (x'+1)
To make this more concise, we can use the Functor
instance for
Maybe
that we saw last week:
fmap (+1) x
The above works because we are applying a pure function of type Int -> Int
to the value in the Maybe
. But what if the function is also
produced from some potentially failing computation, e.g. what if f :: Maybe (Int -> Int)
? Then fmap f x
will be ill-typed, because f
is
not a function - it is a function contained in an effectful
computation (or less abstractly, stored in a Maybe
container).
We can write it using pattern matching, of course:
case f of
Just f' ->
case x of
Just x' -> Just (f' x')
Nothing -> Nothing
Nothing -> Nothing
But all this checking for Failure
becomes quite verbose. Our
salvation comes in the form of another typeclass, Applicative
, which
describes applicative functors. Any applicative functor must also be
an ordinary functor, which we can add as a superclass constraint:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
The pure
method injects a pure value into an effectful computation.
The (<*>)
method applies a function stored in an applicative functor
to a value stored in an applicative functor, yielding an applicative
functor. Essentially, it is an extension of the notion of Functor
to
also allow the function to be in a "container".
This sounds (and is) abstract, and is perhaps best understood by looking at an example instance:
instance Applicative Maybe where
pure x = Just x
f <*> x = case f of
Just f' ->
case x of
Just x' -> Just (f' x')
Nothing -> Nothing
Nothing -> Nothing
Or equivalently:
instance Applicative Maybe where
pure x = Just x
Just f <*> Just x = Just (f x)
_ <*> _ = Nothing
Now we can write f <*> x
rather than writing out the case
by hand.
Even better, this will work not just when f
and x
make use of
Maybe
specifically, but any type that is an applicative functor -
and as we shall see, any monad is also an applicative functor.
Monads
Consider now the case where we have a value x :: Maybe Int
and a function
f :: Int -> Maybe Int
. That is, the function now also has an effect - in
this case, it can fail.
If we use fmap f x
, we get something that is well-typed, but the
result has type Maybe (Maybe Int)
, which is unlikely to be what we
desire. What we need is this function:
maybeBind :: Maybe a -> (a -> Maybe b) -> Maybe b
maybeBind Nothing _ = Nothing
maybeBind (Just x) f = f x
The maybeBind
function passes a value of type a
to a provided
function that operates on a
, but returns a potentially failing
computation (Maybe b
). If the original value is Nothing
, then the
final result is Nothing
. We can now write our application as
maybeBind x f
or using backticks to make the operator infix:
x `maybeBind` f
The intuition here is "first execute x
, then apply the pure result
(if any) to f
".
It turns out that functions with the same "shape" as maybeBind
are
pretty common. For example, we can define a similar one for Either
,
where in the type we simply replace Maybe a
with Either e a
:
eitherBind :: Either e a -> (a -> Either e b) -> Either e b
eitherBind (Left e) _ = (Left e)
eitherBind (Right x) f = f x
Note that we operate only on the Right
part of the Either
value -
the Left
part, which usually represents some kind of error case, is
undisturbed.
We can even define such a function for linked lists:
listBind :: [a] -> (a -> [b]) -> [b]
listBind [] _ = []
listBind (x : xs) f = f x ++ listBind xs f
The type looks a bit different than maybeBind
and eitherBind
, but
that is just because of the syntactic sugar that lets us write [a]
instead of List a
.
It seems that when we have things that behave a bit like "containers"
(in a general sense), we can define these "bind" functions that all
have some similar behaviour. When we observe such similarities, we can
put them into a typeclass. In this case, the typeclass is Monad
, and
the "bind" function is named the much more intuitive and
easy-to-pronounce >>=
:
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
Haskell already provides a Monad
instance for Maybe
, but if it did
not, we would define it as follows:
instance Monad Maybe where
Nothing >>= _ = Nothing
Just x >>= f = f x
Note that this definition is equivalent to the maybeBind
above.
Indeed, we could also write this definition like so:
instance Monad Maybe where
(>>=) = maybeBind
An instance definition also exists for Either
, equivalent to
eitherBind
above. The Maybe
and Either
monads are heavily used
for tracking errors in Haskell code, similar to how we would use
exceptions in other languages.
Intuition, nomenclature, and slang
Monads when viewed as an abstract interface can be quite tricky to get a grasp of. They are so abstract and general that it can be difficult to understand what the general concept means.
One good approach to learning is to essentially disregard the general
concept, and focus only on specific monads. It is not so difficult to
understand operationally what the Monad
instances for Maybe
and
Either
do, and in this we will mostly be working with specific
monads.
Another approach is to focus on their form in Haskell. A monad is
something that implements the Monad
type class, which just means a
we have access to >>=
and pure
, and can do anything these
functions allow.
From a pedantic viewpoint, a function f
of type a -> m b
, where
m
is some monad (such as Maybe
), returns a monadic value of
type m b
. We also sometimes say that it returns a command in the
monad m
which can produce a value of type b
when "executed" (this
term will make a bit more sense for the monads discussed below). We
often also say that f
is a monadic function. This is technically
an abuse of nomenclature, but functions that "run within a specific
monad" are such a common concept in Haskell that terse nomenclature is
useful.
Deriving fmap
and <*>
It turns out that when some type is an instance of Monad
, the fmap
and <*>
methods from Functor
and Applicative
can be expressed in
terms of >>=
and pure
. This means that when we implement this
trifecta of instances, we only really have to think about >>=
and
pure
. Specifically, we can define a function liftM
that behaves
like fmap
for any monad:
liftM :: Monad m => (a -> b) -> m a -> m b
liftM f x = x >>= \x' -> pure (f x')
And similarly a function ap
that behaves like <*>
:
ap :: Monad m => m (a -> b) -> m a -> m b
ap f x = f >>= \f' ->
x >>= \x' ->
pure (f' x')
It can be shown that these are actually the only law-abiding
definitions for these functions. Further, these functions are
available from the builtin Control.Monad
module. This means that
when defining the instances for Maybe
, we can take the following
shortcuts:
import Control.Monad (liftM, ap)
instance Applicative Maybe where
(<*>) = ap
instance Functor Maybe where
fmap = liftM
do
-notation
Monads are particularly ergonomic to use in Haskell, and the main
reason for this is a bit of syntactic sugar called do
-notation,
which allows us to imitate imperative programming.
Roughly speaking, the keyword do
begins a block wherein every line
corresponds to a monadic action, with the actions combined with >>=
,
and each statement after the first beginning a lambda. As an example,
do x <- foo
y <- bar
baz x y
is syntactic sugar for
foo >>=
(\x -> bar >>=
(\y -> baz x y))
We can break a single statement over multiple lines if we are careful about how we indent them: the continuation lines must be indented more deeply than the first:
do x <- foo one_argument
more arguments...
y <- x
...
Examples of defining and using monads
In the following we will look at examples of how to define and use our own monads. Most of these correspond to monads that are available and commonly used in standard Haskell libraries.
The Reader Monad
The Reader monad is a commonly used pattern for implicitly passing an extra argument to functions. It is often used to maintain configuration data or similar context that would be annoyingly verbose to handle manually, perhaps because it is not used in all cases of a function. We will call this implicit value an environment.
Operationally, a Reader monad with environment env
and producing a
value of type a
is represented as a function accepting an env
and
returning a
.
newtype Reader env a = Reader (env -> a)
Coming up with a definition of the Reader type itself requires actual
creativity. On the other hand, the Functor
/Applicative
/Monad
instances can almost be derived mechanically. I recommend starting
with the the following template code:
instance Functor (Reader env) where
fmap = liftM
instance Applicative (Reader env) where
(<*>) = ap
pure x = undefined
instance Monad (Reader env) where
m >>= f = undefined
The fmap
and (<*>)
definitions are done, as discussed above. All
we have to do is fill out the definitions of pure
and >>=
. I
usually start with pure
, because it is simpler. We start with this:
pure x = undefined
The Applicative
class requires that the pure
instance for Reader env
has type a -> Reader env a
. This means we know two things:
x :: a
.- The right-hand side (currently
undefined
) must have typeReader env a
.
Looking at the type definition of Reader
above, we see that any
value of type Reader
takes the form of a Reader
constructor
followed by a payload. So we write this:
pure x = Reader undefined
Looking at the definition of Reader
again, we see that this
undefined
must have type env -> a
.
Instead of using undefined
, we can also use _
. This is a so-called
hole, and will cause the compiler to emit an error message
containing the type of the expression that is supposed to be located at the hole.
How do we construct a value of type env -> a
? Well, we have a
variable of type a
(namely x
), so we can simply write an anonymous
function that ignores its argument (of type env
) and returns x
:
pure x = Reader $ \_env -> x
This concludes the definition of pure
. We now turn our attention to
>>=
. The line of thinking is the same, where we systematically
consider the types values we have available to us, and the types of
values we are supposed to construct. This is our starting point:
m >>= f = undefined
We know:
m :: Reader env a
f :: a -> Reader env b
undefined :: Reader env b
We don't have anything of type a
, so we cannot apply the function
f
. One we can do is deconstruct the value m
, since we know this is
a single-constructor datatype:
Reader x >>= f = undefined
Now we have the following information:
x :: env -> a
f :: a -> Reader env b
undefined :: Reader env b
The values x
and f
are functions for which we do not have values
of the required argument type, so we cannot do anything to. But we can
still start adding a constructor to the right-hand side, just as we
did above for pure
:
Reader x >>= f = Reader undefined
And again, we know that the undefined
here must be a function taking
an env
as argument:
Reader x >>= f = Reader $ \env -> undefined
So far we have not used any creativity. We have simply done the only things possible given the structure of the types we have available. We now have this:
x :: env -> a
f :: a -> Reader env b
env :: env
undefined :: b
Since we now have a variable of type env
, we can apply the function
x
. We do so:
Reader x >>= f = Reader $ \env ->
let x' = x env
in undefined
Now we have x' :: a
, which allows us to apply the function f
:
Reader x >>= f = Reader $ \env ->
let x' = x env
f' = f x'
in undefined
We have f' :: Reader env b
, so we can pattern match on f'
to
extract the payload. When a type has only a single constructor, we can
do this directly in let
, without using case
:
Reader x >>= f = Reader $ \env ->
let x' = x env
Reader f' = f x'
in undefined
Now we have f' :: env -> b
, which is exactly what we need to finish the
definition:
Reader x >>= f = Reader $ \env ->
let x' = x env
Reader f' = f x'
in f' env
This finishes the Monad
instance for Reader
. However, we still
need to define the programming interface for the monad. Some monads
(such as Maybe
, Either
, or lists) directly expose their type
definition. But for more effect-oriented monads like Reader
, we
usually want to hide their definition and instead provide an abstract
interface. This usually takes the form of a function for executing a
monadic computation, as well as various functions for constructing
monadic computations. For Reader
, we will implement the following
interface:
runReader :: env -> Reader env a -> a
ask :: Reader env env
local :: (env -> env) -> Reader env a -> Reader env a
The runReader
function is used to execute a Reader
computation,
given an initial environment. It has the following definition:
runReader env (Reader f) = f env
The ask
command is used to retrieve the environment. It has the
following definition:
ask = Reader $ \env -> env
The local
function executes a given Reader
command in a modified
environment. This does not allow stateful mutation, as the environment
is only modified while executing the provided command, not any
subsequent ones:
local f (Reader g) = Reader $ \env -> g (f env)
When using the Reader
monad, we will exclusively make use of
runReader
, ask
, and local
(and functions defined in terms of
these), and never directly construct Reader
values.
Using the Reader Monad
The Reader
monad is mostly useful when writing functions with many
cases, where only some need to make use of the environment. This means
compelling examples are relatively verbose. You will see such examples
in the course exercises, but for now, we will use a somewhat contrived
example of modifying a binary tree of integers, such that every node
is incremented with its distance from the root.
First we define the datatype.
data Tree
= Leaf Int
| Inner Tree Tree
deriving (Show)
> (Leaf 0 `Inner` Leaf 0) `Inner` Leaf 0
Inner (Inner (Leaf 0) (Leaf 0)) (Leaf 0)
Then we can define a monadic recursive function over Tree
:
incLeaves :: Tree -> Reader Int Tree
incLeaves (Leaf x) = do
depth <- ask
pure $ Leaf $ x + depth
incLeaves (Inner l r) = do
l' <- local (+ 1) $ incLeaves l
r' <- local (+ 1) $ incLeaves r
pure $ Inner l' r'
And then we can run our contrived function on some provided tree:
> runReader 0 $ incLeaves $ (Leaf 0 `Inner` Leaf 0) `Inner` Leaf 0
Inner (Inner (Leaf 2) (Leaf 2)) (Leaf 1)
The State Monad
The State monad is similar to the Reader monad, except now we allow
subsequent commands to modify the state. We represent a stateful
computation as a function that accepts a state (of some abstract type
s
) and returns a new state, along with a value.
newtype State s a = State (s -> (a, s))
The definitions of the type class instances follow similarly to the ones for Reader, and can be derived largely through the same technique of considering the types of values we have available to us.
instance Monad (State s) where
State m >>= f = State $ \state ->
let (x, state') = m state
State f' = f x
in f' state'
Note that the have the opportunity to make a mistake here, by using
the original state
instead of the modified state'
produced by m
.
instance Functor (State s) where
fmap = liftM
instance Applicative (State s) where
pure x = State $ \state -> (x, state)
(<*>) = ap
We also provide the following API for executing and interacting with stateful computations. First, computation requires that we provide an initial state and returns the final state:
runState :: s -> State s a -> (a, s)
runState s (State f) = f s
The put
and get
functions are for reading and writing the state.
get :: State s s
get = State $ \s -> (s, s)
put :: s -> State s ()
put s = State $ \_ -> ((), s)
If we want to store more than a single value as state, we simply store a tuple, record, or some other compound structure.
Typically we also define a utility function modify
for modifying the
state with a function. This does not need to access the innards of the
state monad, but can be defined in terms of get
and put
:
modify :: (s -> s) -> State s ()
modify f = do x <- get
put $ f x
Using the State monad
We will use the State monad to implement a function that renumbers the leaves of a tree with their left-to-right traversal ordering.
numberLeaves :: Tree -> State Int Tree
numberLeaves (Leaf _) = do
i <- get
put (i + i)
pure $ Leaf $ i + 1
numberLeaves (Inner l r) = do
l' <- numberLeaves l
r' <- numberLeaves r
pure $ Inner l' r'
We may now use it as follows:
> runState 0 $ numberLeaves $ (Leaf 0 `Inner` Leaf 0) `Inner` Leaf 0
(Inner (Inner (Leaf 0) (Leaf 1)) (Leaf 2),3)
Combining Reader and State
One limitation of monads - in particular the simple form we study in
AP - is that they do not compose well. We cannot in general take two
monads, such as Reader
and State
, and combine them into a single
monad that supports both of their functionalities. There are
techniques that allow for this, such as monad transformers, but they
are somewhat complex and outside the scope of AP. Instead, if we wish
to have a monad that supports both a read-only environment (such as
with Reader
) and a mutable store (such as with State
), then we
must write a monad from scratch, such as the following RS
monad.
newtype RS env s a = RS (env -> s -> (a, s))
See how the function we use to represent the monad takes two
arguments, env
and s
, corresponding to the environment and store
respectively, but returns only a new store.
The Monad
instance itself is a little intricate, but it just
combines the dataflow that we also saw for the Reader
and State
monads above:
instance Monad (RS env s) where
RS m >>= f = RS $ \env state ->
let (x, state') = m env state
RS f' = f x
in f' env state'
The Functor
and Applicative
instances are then just the usual
boilerplate.
instance Functor (RS env s) where
fmap = liftM
instance Applicative (RS env s) where
pure x = RS $ \_env state -> (x, state)
(<*>) = ap
We can then define the following API for RS
, providing both State
and Reader-like operations:
runRS :: env -> s -> RS env s a -> (a, s)
runRS env state (RS f) = f env state
rsGet :: RS env s s
rsGet = RS $ \_env state -> (state, state)
rsPut :: s -> RS env s ()
rsPut state = RS $ \_env _ -> ((), state)
rsAsk :: RS env s env
rsAsk = RS $ \env state -> (env, state)
rsLocal :: (env -> env) -> RS env s env -> RS env s env
rsLocal f (RS g) = RS $ \env state -> g (f env) state