Reader
As a reminder, here's the definition of the Reader
functor:
newtype Reader r a = Reader { runReader :: r -> a }
Let's verify that Reader
is indeed a functor. Remember that I said before that
we can think of a function—and Reader
is just a thin newtype
wrapper around
a function—as a container that stores values of type a
indexed by values of
type r
. Now consider a container xs = Reader g
, where g :: r -> a
, and
assume we have a function f :: a -> b
. Then fmap f xs = Reader h
, for some
function h
. What should this function look like? First, it should have the
type h :: r -> b
. Second, not changing the "shape" of the container means
that, for every index element i :: r
, h i
should return the result of
applying f
to g i
: the element at "position" i
in xs
is replaced with
the result of applying f
to it. Let me say this one more time: For every
i :: r
, we want that
h i = f (g i)
Looks like function composition to me:
h = f . g
This gives us our Functor
instance for the Reader
functor:
instance Functor (Reader r) where
fmap f r = Reader $ f . runReader r
Well, this is maybe a bit terse. So let's spell out what we really want. We want that
fmap f (Reader g) = Reader (f . g)
Let's rewrite this until we obtain the definition in the Functor
instance for
Reader r
:
fmap f (Reader g) = Reader (f . g)
= Reader $ f . g -- Definition of ($)
= Reader $ f . runReader (Reader g) -- Definition of runReader
fmap f r = Reader $ f . runReader r -- Substitute r for (Reader g)
We have to check the functor laws:
-
Identity:
fmap id (Reader g) = Reader $ id . runReader (Reader g) -- Definition of fmap = Reader $ runReader (Reader g) -- Definition of id = Reader g -- Definition of runReader = id (Reader g) -- Definition of id
-
Composition:
fmap (h . f) (Reader g) = Reader $ (h . f) . runReader (Reader g) -- Definition of fmap = Reader $ (h . f) . g -- Definition of runReader = Reader $ h . (f . g) -- Associativity of (.) = Reader $ h . runReader (Reader (f . g)) -- Definition of runReader = fmap h (Reader (f . g)) -- Definition of fmap = fmap h (Reader $ f . runReader (Reader g)) -- Definition of runReader = fmap h (fmap f (Reader g)) -- Definition of fmap = (fmap h . fmap f) (Reader g) -- Definition of (.)
To make this a little less abstract, here is a function that multiplies its
argument by 2, only we express it as a Reader
:
>>> :{
| newtype Reader r a = Reader { runReader :: r -> a }
|
| instance Functor (Reader r) where
| fmap f r = Reader $ f . runReader r
| :}
>>> double = Reader (* 2)
Remember, viewed as a container, double
stores one value for every possible
argument of type Int
. The value that it stores at index i
is 2 * i
:
>>> runReader double 2
4
>>> runReader double 10
20
Now we want to turn this into a container where each value x
is replaced with
its string representation:
>>> doubleString = fmap show double
Given that we had the values 4 and 20 at indices 2 and 10 in double
, we expect
the values at indices 2 and 10 in doubleString
to be "4" and "20". That's
exactly what we get:
>>> runReader doubleString 2
"4"
>>> runReader doubleString 10
"20"
All this may seem like some really weird and unnecessary acrobatics. After all,
Reader
is just a wrapper around a function:
>>> double = (*) 2
>>> double 2
4
>>> double 10
20
and fmap
is just function composition:
>>> doubleString = show . double
>>> doubleString 2
"4"
>>> doubleString 10
"20"
All we have done is to add a whole lot of boilerplate to our code, and
boilerplate is usually never good. The Reader
functor is nevertheless very
useful, because we will see that we can use it to express computations that
depend on some read-only context. Reader
helps us to thread this context
through the entire computation rather conveniently. We'll discuss this in the
chapter on monads.