Haskell Book: Chapter 22: Reader

Note: This was translated from markdown to medium via markdowntomedium.com.

Reader is a type that is useful when we want to make some information available intermittently or universally throughout an application. Conceptually, the Reader type allows us to read a value from the environment. Mechanically, Reader is a newtype wrapper for a function of type r -> a.

Functor of Functions

In order to understand how Reader works, it is necessary to understand how the functor of functions works.

In short, the functor of functions is simply function composition:

The above may look a bit strange but we can rearrange the (specialized) type signature:

(a -> b) -> ((->) r a) -> ((->) r b) ~ (a -> b) -> (r -> a) -> (r -> b)

r, which represents the argument to the function, is part of the structure that we are mapping over. It is therefore not possible to change it. Instead, we will transform the value returned by the function. In our case, the value of type a will be transformed into a value of type b.

So, for functions, we can read the type signature as saying fmap takes a function from a to b, a function from r to a, and returns another function from r to b. How? Through function composition.

Applicative of Functions

The Reader newtype also has an applicative instance:

pure a will give us back a Reader that, applied to an argument r, discards the r and gives us back the a.

(<*>) is more interesting. In this case, the first argument to <*> - Reader r (a -> b) - is a Reader that wraps a function with the signature r -> (a -> b). That is more commonly writen as r -> a -> b. The second argument is a function (wrapped in Reader) that, when applied to an argument r produces a value a. From the implementation, the <*> for Reader returns back a wrapped function that, given a value r, applies that to the second record to get an a, and then uses that a and the r to fully saturate the function wrapped in the first Reader.

In other words, <*> will give us back a Reader that when "ran" will run the two readers we gave it, one after the other, and use the output of one as part of the input of the other.

The applicative of functions is very instructive. If we look at the definition of <*> again, we can see that both Readers are waiting for the same input - r. The first Reader is awaiting even more input, but that's OK. The point is that, if we have a function r -> a -> b and a function r -> a, we can compose in such a way that we get back a function simply waiting an r to give us a b. We no longer have to manually worry about getting the a, that will be handled for us.

One of the things we have to understand about Applicative an applicative allows us to apply a function in a structure to a value in a structure. It combines the structures for us. A perfect example is the following:

Let’s expand the last expression above:

Now, let’s take one reduction step We know that fmap / <$> is going to lift the function (, ) over the "Maybe container" and apply it to the value inside:

(Note that we can write (6, ); it should be (, ) 6, but conceptually it is easier to use (6, )).

Now, for the interesting part, <*> is actually going to apply the function inside our Maybe container to the value inside another maybe container, and "merge" the containers:

This is how we go from two values of type Maybe Integer to Maybe (Integer, Integer. Of course, if any of our values had been Nothing, the result of the entire expression would have been Nothing.

Functor / Applicative of Functions

This is a tricky subject, so we are going to try this again:

The bolt function takes an integer and calculates if the integer is greater than 3 and greater than 8. But how is it doing that?

First, let’s remember that the functor of functions is function composition, so that (&&) <$> (> 3) is the same as (&&) . (> 3) - i.e., a function that will pipe its argument to (> 3) and then to (&&). Let's reduce:

The type of that expression is a -> Bool -> Bool (with constraints elided).

Now, we know that <*> takes a function "inside a container" and a value inside the container, and applies the function to the value and "merges" the container:

Let’s re-arrange our type from a -> Bool -> Bool to a -> (Bool -> Bool). We can then further rearrange this to (->) a (Bool -> Bool). This shows more clearly that our container is ((->) a), and our function is Bool -> Bool.

Let’s do the same with the expression > 8. It's type is a -> Bool (constraints elided), which we can rearrange to (->) a Bool. Now, we have our two applicatives: (->) a (Bool -> Bool) and (->) a Bool. If we were to "apply and merge" these via <*>, we would apply Bool -> Bool from the first type to the Bool in the second type, to get (->) a Bool.

Let’s go back to the value level:

If we look at how <*> is defined for functions, we see that: f <*> g = \a -> f a (g a). So we can reduce bolt again to this:

So there we go. We’ve used the substitution model, essentially just plugging in the expressions in the right place, to figure out how to reduce <*>. We can see how liftA2 gets us the function we wanted.

In the bolt example, what is hard to recognize is what the Applicative of functions is. It is easy to figure it out for Maybe a values. Forgetting about Nothing, we take a value Just someFunction and another value Just argument and we return Just (someFunction argument). No biggie, but applicative of functions?