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 b
return :: a -> m a
{-# MINIMAL (>>=) #-}

instance Monad (Either e)
instance Monad []
instance Monad Maybe
instance Monad IO
instance 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 b
return :: 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:

  1. Impure. Monadic functions are pure functions.

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

  3. A value. Monads are type classes (or algebras, as a general concept), not values.

  4. 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 each Maybe computation fails or succeeds independently of each other. You’re lifting functions that are also Just or Nothing over Maybe values.

  • With the Maybe Monad, computations contributing to the final result can choose to return Nothing 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)