Chapter 22: Reader

Hey; Stop! This chapter sucks, and so do my notes on it. Go read `this simple example <https://blog.ssanj.net/posts/ 2014-09-23-A-Simple-Reader-Monad-Example.html >`_, instead.

22.1 Reader

In this chapter, we will:

  • examine the Functor, Applicative, and Monad type class instances for functions;

  • learn about the Reader newtype;

  • and see some examples of using Reader.

22.2 A new beginning

This section demonstrates how the instances of Functor, Applicative, and Monad work for functions.

Why is this relevant to Reader? Well, Reader is implemented as a wrapper for the function type constructor, so the behaviour listed here applies to the class methods of Reader.

Here is a recording of me following along with the section.

The Functor instance for functions

fmap applies functions to each element within a structure. Some of these structures make intuitive sense, like lists. But, did you know you can also fmap a function over another function?

In the case of fmap’ping a function over a function, what is the containing structure, and what are the elements that we operate on within that structure?

Answer: The structure is a partially applied function, and the elements are the arguments to that function.

So something like this:

fmap  (\x -> (+) 10 x)  (\y -> (*) 2 y)

Will evaluate to this:

\y -> (*) 2 ((\x -> (+) 10 x) y)

As you can see, the function we map gets applied to the argument of our containing function, meaning that the function f must act on the result of it’s argument with g applied. This is really just function composition.

Here’s what the definition of fmap looks like, taken from GHC.Base at the time of this writing:

instance Functor ((->) r) where
  fmap = (.)

The Applicative instance for functions

Let’s look at how sequential application works, now:

-- page 846, figure 4

bbop :: Integer -> Integer
bbop = (+) <$> boop <*> doop

duwop :: Integer -> Integer
duwop = liftA2 (+) boop doop

Around this area in the book, there are a few remarks explaining how liftA2 is evaluated, but I found them mostly unhelpful. There are also some examples, but I don’t understand them. I really tried to; But I don’t.

Instead, here is the Applicative instance for functions:

instance Applicative ((->) r) where
  pure            =          const
  (<*>)    f g x  =     f x  (g x)
  liftA2 q f g x  =  q (f x) (g x)

…and here is a transcript of me mechanically desugaring an expression that uses (<*>) in GHCi:

-- Here's a small ghci session where I
-- desugar an expression that uses the
-- Applicative instance of functions to get
-- a feel for it.
--
-- Desugaring the expression:
--
--   Prelude> bbop 10
--
-- Where bbop has the definition
-- (from figure 4):
--
--   bbop = (+) <$> boop <*> doop
--
-- Using the definition for (<*>) of...
--
--   (<*>) f g x = f x (g x)
--
-- ( Taken from here https://hackage.haskell.org/
-- package/base-4.14.0.0/docs/src/GHC.Base.html
-- #line-969 )
--
-- ...and a definition of fmap of..
--
--   fmap = (.)
--
-- ...and the definition of (.) of...
--
--   (.) f g x = f (g x)
--


·∾ ((+) <$> boop <*> doop) 10
40


·∾ ((\x -> (+) (boop x)) <*> doop) 10
40


·∾ :{
·∾ ((<*>)
⋮  (\x -> (+) (boop x))
⋮  doop
⋮ )
⋮ 10
⋮ :}
40


·∾ -- [  (<*>)  :=  (\f g x -> f x (g x))  ]
·∾ :{
⋮ ((\f g x -> f x (g x))
⋮  (\z -> (+) (boop z))
⋮  doop
⋮ )
⋮ 10
⋮ :}
40


·∾ -- [  f  :=  (\z -> (+) (boop z))  ]
·∾ :{
⋮ ((\g x -> (\z -> (+) (boop z)) x (g x))
⋮  doop
⋮ )
⋮ 10
⋮ :}
40


·∾ -- [ g := doop ]
·∾ :{
⋮ (\x -> (\z -> (+) (boop z)) x (doop x))
⋮ 10
⋮ :}
40


·∾ -- [ x := 10 ]
·∾ (\z -> (+) (boop z)) 10 (doop 10)
40


·∾ -- [ z := 10 ]
·∾ ((+) (boop 10)) (doop 10)
40


·∾ (+) (boop 10) (doop 10)
40


·∾ boop 10 + doop 10
40

The Monad instance for functions

This example does the same thing as the last one, but is phrased to use the Monad instance of (->), instead of Applicative:

boopDoop :: Integer -> Integer
boopDoop = do
  a <- boop
  b <- doop
  return (a + b)

Here is the instance definition, so we can decode it:

instance Monad ((->) r) where
  return          =   const
  f >>= k         =   \r -> k (f r) r

…and here I go, desugaring boopDoop:

boop = (*2)
doop = (+10)


bip = boop . doop


boopDoop :: Integer -> Integer
boopDoop = do
  a <- boop
  b <- doop
  return (a + b)


{- Given that,
 -
 -  instance Monad ((->) r) where
 -    return   =  const
 -    f >>= k  =  \r -> k (f r) r
 -
 - desugar the do block in boopDoop.
 -}


-- [ do { ; } := (>>=) ]
boopDoop' =
  boop >>= (\a ->
    doop >>= (\b ->
      return (a + b)))


-- [ return := const ]
boopDoop'' =
  boop >>= (\a ->
    doop >>= (\b ->
      const (a + b)))


--   f >>= k  =  \r -> k (f r) r

boopDoop''' =
    boop >>= (\a -> doop >>= (\b -> const (a + b)))


--     f >>= k  =  \r -> k (f r) r


--  [ f := boop ]
--
--     boop >>= k  =  \r -> k (boop r) r


--  [ k := (\a -> doop >>= (\b -> const (a + b))) ]
--
--     \r -> (\a -> doop >>= (\b -> const (a + b))) (boop r) r

--     \r -> (\a ->
--        doop >>= (\b -> const (a + b))
--        ) (boop r) r
--
--  [
--    doop >>= (\b -> const (a + b))
--    :=
--    (\v -> (\b -> const (a + b)) (doop v) v)
--  ]
--

{-# ANN boopDoopDesugared "Hlint: Ignore redundant lambda"  #-}
boopDoopDesugared =
   (\r ->
     (\a ->
       (\v ->
          (\b -> const (a + b))
          (doop v)
          v
       )
     )
     (boop r)
     r
   )

22.2.1 Short Exercise: Warming up

We’ll be doing something here similar to what you saw above, to give you practice and help you try to develop a feel or intuition for what is to come. These are similar enough to what you just saw that you can almost copy and paste, so try not to overthink them too much.

First, start a file off like this:

import Data.Char


cap :: [Char] -> [Char]
cap xs = map toUpper xs


rev :: [Char] -> [Char]
rev xs = reverse xs

Two simple functions with the same type, taking the same type of input. We could compose them, using the (.) operator or fmap:

composed :: [Char] -> [Char]
composed = undefined


fmapped :: [Char] -> [Char]
fmapped = undefined

The output of these two functions should be identical, one string that is made all uppercase and reversed, like this:

Prelude> composed "Julie"
"EILUJ"

Prelude> fmapped "Chris"
"SIRHC"

We want to return the results of cap and rev as a tuple, like this:

Prelude> tupled "Julie"
("JULIE","eiluJ")

…or this:

Prelude> tupled' "Julie"
("eiluJ","JULIE")

We want to use an Applicative here. The type looks like this:

tupled :: [Char] -> ([Char], [Char])

There is no special reason such a function needs to be monadic, but let’s do that, too, to get some practice. Do it one time using do syntax. Then try writing a new version using (>>=). The types will be the same as the type for tupled.

Here is my attempt:

module Lib
  ( cap
  , rev
  , composed
  , fmapped
  , tupled
  , tupled'
  , tupledM
  , tupledM'
  ) where
import Data.Char
import Control.Applicative (liftA2)


{-# ANN cap "HLint: ignore Eta reduce" #-}
cap :: String -> String
cap xs = map toUpper xs


{-# ANN rev "HLint: ignore Eta reduce" #-}
rev :: String -> String
rev xs = reverse xs


composed :: String -> String
composed = cap . rev


fmapped :: String -> String
fmapped = cap <$> rev


-- Use applicative to define these functions.
tupled :: String -> (String, String)
tupled = (,) <$> cap <*> rev


tupled' :: String -> (String, String)
tupled' = liftA2 (,) rev cap

-- Write these using do syntax, then with (>>=).
-- I only figured this one out because the
-- example in fig16 is so similar.
tupledM :: String -> (String, String)
tupledM = do { x <- cap; y <- rev; return (x,y) }


-- (>>=) x f r = f (x r) r
--
tupledM' :: String -> (String, String)
tupledM' =
  rev >>= (\x
    -> cap >>= (\y
      -> return (x, y)))

22.3 This is Reader

Usually when you see the term Reader, it is used to refer to the Monad instance of the function type constructor.

Reader is all about reading an argument from the environment into functions.

22.4 Breaking down the Functor of functions

Specialize the f in fmap’s type signature to the function constructor and you get (.):

fmap :: Functor f =>   (a -> b)  ->  f a       ->  f b
-- [ f := ((->) r) ]
fmap ::                (a -> b)  ->  (r -> a)  ->  (r -> b)
-- [ b := c ]
fmap ::                (a -> c)  ->  (r -> a)  ->  (r -> c)
-- [ a := b ]
fmap ::                (b -> c)  ->  (r -> b)  ->  (r -> c)
-- [ r := a ]
fmap ::                (b -> c)  ->  (a -> b)  ->  (a -> c)
(.)  ::                (b -> c)  ->  (a -> b)  ->  (a -> c)

22.5 But uh, Reader?

Reader is a newtype wrapper for the function type:

newtype Reader r a =
  Reader { runReader :: r -> a }

The r is the type we’re reading in, and a is the result type of our function.