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
, andMonad
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.