Chapter 18: Monad¶
18.1 Monad¶
In this chapter, we:
define Monad, its operations and laws;
look at several examples of monads in practice;
write the Monad instances for various types;
address some misinformation about monads.
18.2 Sorry – a monad is not a burrito¶
A monad is a pattern of function application.
When learning monads, Stephen Dhiel says to keep in mind: “A monad is just its algebraic laws. Nothing more, nothing less. Analogies or metaphors will not lead to understanding. Only using monads in code will.”
First let’s take a look at the type class definition:
class Applicative m => Monad (m :: * -> *) where(>>=) :: m a -> (a -> m b) -> m b(>>) :: m a -> m b -> m breturn :: a -> m a{-# MINIMAL (>>=) #-}instance Monad (Either e)instance Monad []instance Monad Maybeinstance Monad IOinstance Monad ((->) r)instance Monoid a => Monad ((,) a)
18.2.1 Applicative m¶
You can derive applicative and functor in terms of monad, just as you can derive functor in terms of applicative.
What does this mean? It means that, for
example, that you can write fmap
using
monadic operations and it works:
fmap f xs
\(=\)xs >>= return . f
For example:
·∾ fmap (+1) [1,2,3]
[2,3,4]
·∾ [1,2,3] >>= return . (+1)
[2,3,4]
Try it for yourself:
It’s important to understand this chain of dependency:
Functor
->Applicative
->Monad
So whenever you implement an instance of monad for a type, you necessarily have an applicative and a functor as well.
18.2.2 Core operations¶
The Monad
type class defines three core
operations, although you only need to
define (>>=)
for a minimally complete
instance.
Let’s look at all three:
(>>=) :: m a -> (a -> m b) -> m b(>>) :: m a -> m b -> m breturn :: a -> m a
(Notice something?)
return
puts something inside a context, it
has a default class method of pure = return
.
(>>)
sequences two actions while
discarding any resulting value of the first
action.
(>>=)
is what we’ll talk about next.
18.2.3 The novel part of Monad¶
What’s unique to monad, at least from the point of view of types?
We already saw that it’s not return
.
It also isn’t (>>)
. And it also isn’t
(>>=)
, at least not in its entirety.
The type of (>>=)
is visibly similar to
that of fmap
and (<*>)
, which makes
sense since monads are applicative functors.
For the sake of making this maximally similar, we’re going to change the m of monad to f:
fmap :: Functor f
=> (a -> b) -> f a -> f b
(<*>) :: Applicative f
=> f (a -> b) -> f a -> f b
(>>=) :: Monad f
=> f a -> (a -> f b) -> f b
So, the idea of mapping a function over a value while bypassing its surrounding structure is not unique to monad.
We can demonstrate this by fmapping a
function of type (a -> m b)
to make it
more like (>>=)
, and it will work:
·∾ :type fmap
fmap :: Functor f => (a -> b) -> f a -> f b
·∾ :{
⋮ fmap' :: Functor f => (a -> f b) -> f a -> f (f b)
⋮ fmap' = fmap
⋮ :}
·∾ :type fmap'
fmap' :: Functor f => (a -> f b) -> f a -> f (f b)
After mapping a function that generates additional monadic structure in its return type, we want a way to discard on layer of that structure. So, how do we accomplish that?
Well, at least with lists, we already know how:
·∾ :type concat
concat :: Foldable t => t (t a) -> t a
The module Control.Monad
provides a
similar function, join
:
·∾ import Control.Monad (join)
·∾ :type join
join :: Monad m => m (m a) -> m a
Monad, in a sense, is a generalization of
concat
!
·∾ import Control.Monad (join)
·∾ :set -XTypeApplications
·∾ :type concat @[]
concat @[] :: [[a]] -> [a]
·∾ :type join @[]
join @[] :: [[a]] -> [a]
Allowing the function itself to alter the
structure is something we’ve not seen in
Functor
and Applicative
. The ability
to flatten those two layers of structure
into one is what makes Monad
special.
By putting that join
function together with
fmap
, we can create bind.
So how do we get bind?
·∾ :{
⋮ bind :: Monad m => (a -> m b) -> m a -> m b
⋮ bind f ma = join (fmap f ma)
⋮ :}
·∾ :type bind
bind :: Monad m => (a -> m b) -> m a -> m b
·∾ :type flip (>>=)
flip (>>=) :: Monad m => (a -> m b) -> m a -> m b
·∾ :type join
join :: Monad m => m (m a) -> m a
·∾ -- a ~ m a
18.2.4 What Monad is not¶
A monad is not:
Impure. Monadic functions are pure functions.
An embedded language for imperative programming. While monads are often used for sequencing actions in a way that looks like imperative programming, there are commutative monads that do not order operations.
A value. Monads are type classes (or algebras, as a general concept), not values.
About strictness. The monadic operations of bind and return are nonstrict.
The Monad
type class is generalized
structure manipulation with some laws to
make it sensible. Just like Functor
and
Applicative
. That’s all there is to it.
18.2.5 Monad also lifts!¶
The monad type class also includes a set of
lift
functions that are the same as the
ones we already saw in Applicative
. They
don’t do anything different, but they are
still around because some libraries used
them before applicatives were discovered.
18.3 Do syntax and monads¶
Do syntax works with any monad, not just IO.
This section is going to talk about why
do
is sugar and demonstrate what the
join
of Monad
can do for us.
To begin with, let’s look at some correspondences:
(*>) :: Applicative f => f a -> f b -> f b
(>>) :: Monad m => m a -> m b -> m b
For our purposes, (*>)
and (>>)
are
the same thing, sequencing functions, but
with two different constraints.
We can see what do syntax looks like after the compiler desugars it for us by manually transforming it ourselves:
module SequencingAndBinding where
import Control.Applicative ((*>))
-- 177
sequencing :: IO ()
sequencing = do
putStrLn "blah"
putStrLn "another thing"
sequencing' :: IO ()
sequencing' =
putStrLn "blah" >>
putStrLn "another thing"
sequencing'' :: IO ()
sequencing'' =
putStrLn "blah" *>
putStrLn "another thing"
binding :: IO ()
binding = do
name <- getLine
putStrLn name
binding' :: IO ()
binding' =
getLine >>= putStrLn
18.3.1 When fmap alone isn’t enough¶
This won’t work:
·∾ import Control.Monad
·∾ putStrLn <$> getLine
Will this print?
Why? Look at the type signature:
·∾ :type putStrLn <$> getLine
putStrLn <$> getLine :: IO (IO ())
This will fix it:
·∾ join (putStrLn <$> getLine)
Will *this* print?
Will *this* print?
What join did here is merge the effects
of getLine
and putStrLn
into
a single IO action. As it happens, the
cleanest way to express ordering in a lambda
calculus is through nesting expressions.
A more thorough exploration:
Let’s get back to desugaring do
syntax
with our now-enriched understanding of what
monads do for us:
module BindingAndSequencing where
import Control.Applicative ((*>))
-- page 751
bindingAndSequencing :: IO ()
bindingAndSequencing = do
putStrLn "name pls:"
name <- getLine
putStrLn ("y helo thar: " ++ name)
bindingAndSequencing' :: IO ()
bindingAndSequencing' =
putStrLn "name pls:" >>
getLine >>=
\name ->
putStrLn ("y helo thar: " ++ name)
twoBinds :: IO ()
twoBinds = do
putStrLn "name pls:"
name <- getLine
putStrLn "age pls:"
age <- getLine
putStrLn ("y helo thar: "
++ name ++ " who is: "
++ age ++ " years old.")
twoBinds' :: IO ()
twoBinds' =
putStrLn "name pls:" >>
getLine >>=
\name ->
putStrLn "age pls:" >>
getLine >>=
\age ->
putStrLn ("y helo thar: "
++ name ++ " who is: "
++ age ++ " years old.")
One key insight is how name binding with the
assignment arrow like do { name <- getLine;
putStrLn name }
translates into bind, like
getLine >>= (\name -> putStrLn name)
.
When the function to be sequenced doesn’t return
a value that is later used, the “semicolon”
translates into (>>)
.
18.4 Examples of Monad use¶
What we need now is to see how monads work
in code, with Monad
’s other than IO.
18.4.1 List¶
18.4.1.1 Specializing the types¶
-- 18.4 Examples of Monad use
-- 18.4.1 List
-- 18.4.1.1 Specializing the types
-- page 760
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(>>=) :: [ ] a -> (a -> [ ] b) -> [ ] b
-- same as pure
return :: Monad m => a -> m a
return :: a -> [ ] a
-- or more syntactically common
(>>=) :: [a] -> (a -> [b]) -> [b]
return :: a -> [a]
18.4.1.2 Example of the List Monad in use¶
twiceWhenEven :: [Integer] -> [Integer]
twiceWhenEven xs = do
x <- xs
if even x
then [x*x, x*x]
else [x*x]
The x <- xs
line binds individual
values out of the list input, like a list
comprehension, giving us an a
for use
with (>>=)
. The if-then-else is our
(a -> m b)
.
·∾ :load figures/18.4/TwiceWhenEven.hs
[1 of 1] Compiling TwiceWhenEven
( figures/18.4/TwiceWhenEven.hs, interpreted )
Ok, one module loaded.
·∾ twiceWhenEven [1,2,8,5]
[1,4,4,64,64,25]
18.4.2 Maybe Monad¶
18.4.2.1 Specializing the types¶
-- m ~ Maybe
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
-- same as pure
return :: Monad m => a -> m a
return :: a -> Maybe a
18.4.2.2 Using the Maybe Monad¶
Whenever you interact with something within
the Maybe
monad, there is a potential for
the value to be Nothing
. Using the Maybe
Monads do notation, each subsequent line (or
action) in the do block depends on the
previous action not being Nothing
to
continue evaluating the Just
value on
the next line.
The files figures/18.4/MaybeMonadV{1,2,3}.hs
demonstrate a few ways of writing this. But
here are snippets of the changed function
between all three versions of the module, for
convenience:
module MaybeMonadV1 where
. . .
mkSphericalCow :: String -> Int -> Int -> Maybe Cow
mkSphericalCow name' age' weight' =
case noEmpty name' of
Nothing -> Nothing
Just nammy ->
case noNegative age' of
Nothing -> Nothing
Just agey ->
case noNegative weight' of
Nothing -> Nothing
Just weighty -> weightCheck
(Cow nammy agey weighty)
. . .
module MaybeMonadV2 where
. . .
-- With do syntax things are much more concise.
mkSphericalCow' :: String -> Int -> Int -> Maybe Cow
mkSphericalCow' name' age' weight' = do
nammy <- noEmpty name'
agey <- noNegative age'
weighty <- noNegative weight'
weightCheck (Cow nammy agey weighty)
. . .
module MaybeMonadV3 where
. . .
-- Here it is, rewritten to use (>>=), just because.
mkSphericalCow'' :: String -> Int -> Int -> Maybe Cow
mkSphericalCow'' name' age' weight' =
noEmpty name' >>=
\nammy ->
noNegative age' >>=
\agey ->
noNegative weight' >>=
\weighty ->
weightCheck (Cow nammy agey weighty)
Here’s a terminal recording of me interacting with those functions. Spoiler alert: they do the same thing.
At this point, you may have noticed a
similarity between how the Maybe monad behaves
and how Maybe works with applicative. Can we
use Applicative
, instead of Monad
?
Well, if your do syntax looks like this:
doSomething = do
a <- f
b <- g
c <- h
pure (a, b, c)
Then you can rewrite it using Applicative
.
On the other hand, if you have something like this:
doSomething' = do
a <- f n
b <- g a
c <- h b
pure (a, b, c)
You’re going to need a Monad
because g
and h
are producing monadic structure
based on values that can only be obtained by
depending on values generated from monadic
structure.
The long and short of it:
With the
Maybe
Applicative
eachMaybe
computation fails or succeeds independently of each other. You’re lifting functions that are alsoJust
orNothing
overMaybe
values.With the
Maybe
Monad
, computations contributing to the final result can choose to returnNothing
based on previous computations.
18.4.3 Either¶
18.4.3.1 Specializing the types¶
Why do we keep on doing this? To remind you that the types always show you the way, once you’ve figured them out.
18.4.3.2 Using the Either Monad¶
As you can see, Either
always short-circuits
on the first thing to have failed. It must, because
in the monad later values can depend on previous ones.
18.4.3.3 Short Exercises: Either Monad¶
Implement the Either Monad
.
data Sum a b = First a | Second b deriving (Eq, Show)
instance Functor (Sum a) where
fmap = undefined
instance Applicative (Sum a) where
pure = undefined
(<*>) = undefined
instance Monad (Sum a) where
return = pure
(>>=) = undefined
Alright, here is an attempt.
module Lib where
data Sum a b = First a | Second b deriving (Eq, Show)
instance Functor (Sum b) where
fmap f (Second b) = Second (f b)
fmap _ (First a) = First a
instance Applicative (Sum b) where
(First a) <*> _ = First a
(Second f) <*> r = fmap f r
-- The condition where First is on the right
-- would be a type error, since we can't access
-- the first tyvar of Either, used by the First
-- data constructor, so we don't have
-- to worry about defining any logic for it.
pure = Second
instance Monad (Sum b) where
return = pure
(Second b) >>= f = f b
(First a) >>= _ = (First a)
18.5 Monad laws¶
18.5.1 Identity laws¶
Basically both of these laws are saying that
return
should be neutral and not perform
any computation.
Right identity:
m >>= return
\(=\)m
Left identity:
return x >>= f
\(=\)f x
18.5.2 Associativity¶
Associativity:
(m >>= f) >>= g
\(=\)m >>= (\x -> f x >>= g)
Rewritten for more visual similarity:
(m >>= (\x -> g x)) >>= (\y -> h y) ≡ m >>= (\x -> g x >>= (\y -> h y))
…this time, using
Control.Monad.((>=>))
, instead of the(>>=)
operator:(f >=> g) >=> h
\(=\)f >=> (g >=> h)