Functor, Applicative and Monad instances for Reader
How do you define a Reader (-> r) instance of a Functor, Applicative or even a Monad? A Reader is a function that takes some resource r and returns another value. This has been something that has always confused me. After an initial peruse it all makes sense for a while but when next faced with the same problem I can’t remember how these instances are implemented.
I’d like to analyse how the Reader instances are derived for each of Functor, Applicative and Monad and test it against some examples to gain some intuition. Also note that ((->) r) and (r ->) can be used interchangeably. Thanks to Brian McKenna for that useful titbit.
Functor
A functor typeclass is defined as:
class Functor f where
fmap, (<$>) :: (a -> b) -> f a -> f b
fmap or <$> basically runs a function (a -> b), on a value within some context f a and returns the context with the function applied to its value as an f b.
-> b) -- f', a function that requires an 'a' to create a 'b'
(a -- Functor with an 'a'
f a -- apply f' to the 'a'
f (f'(a)) -- the final result of a 'b' f b
Let’s take a look at the Functor instance for Maybe:
instance Functor Maybe where
-- fmap :: (a -> b) -> f a -> f b
fmap f (Just a) = Just (f a)
fmap _ Nothing = Nothing
With Maybe, the function f, is applied to a value within a Just or not applied if the value is a Nothing.
When we hear that (-> r) is also a Functor it can boggle our minds a little. How do we define an instance for that?
instance Functor (-> r) where
fmap f = -- what goes here?
We need a function that takes some resource r and returns some other value. Let’s have a crack at deriving the implementation for Functor:
instance Functor (r -> ) where
-- fmap :: (a -> b) -> f a -> f b
fmap fab f a = f b -- refer to (a -> b) as fab
fmap fab (\r -> a) = (\r -> b) -- given that the Functor is (r ->), replace 'f' with (r ->)
fmap fab fra = (\r -> b) -- refer to (r -> a) as fra so we can use it
fmap fab fra = (\r -> ??? (fra r)) -- we have an 'r' and we have something that needs an 'r' and returns an 'a'.
fmap fab fra = (\r -> fab (fra r)) -- We have an 'a' and something that needs an 'a' to return a 'b'
fmap fab fra = fab . fra -- we can simplify this to composing fab and fra
We are applying the function fab to the result of fra. It looks like fmap takes two functions are composes them.
Compose (.) is defined as:
(.) :: (b -> c) -> (a -> b) -> a -> c
or in our case:
(.) :: (a -> b) -> (r -> a) -> r -> b
And we can implement the Functor for (r ->) with compose alone:
instance Functor (r -> ) where
fmap = (.)
This gives us the intuition that fmap over functions is just composition.
Let’s use it on an example:
fmap (*3) (+100) 1
What is the result of the above?
Let’s use function composition to get the answer:
fmap (*3) (+100) 1
= (\r -> (r + 100) * 3) -- expanding Functor
= ((1 + 100) * 3) -- substituting 1 for 'r'
= 303
Applicative
The Applicative typeclass is defined as:
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
The pure function lifts some value a into the Applicative, f. Also note that f is also a Functor. The <$> function sequences a function from (a -> b) within an Applicative context, with the value of a supplied in another Applicative context to produce the result b in a third Applicative context.
Note the similarities between <$> and <*>:
fmap, (<$>) :: (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b
The only difference is that with <*> the function is within a context f.
-> b) -- f', a function within a context 'f', requires an 'a' to create a 'b'
f (a -- Applicative Functor with an 'a'
f a -- apply f' to the 'a' within 'f'
f (f'(a)) -- the final result of a 'b' f b
Let’s take a look at the Applicative instance for Maybe:
instance Applicative Maybe where
-- pure :: a -> f a
pure = Just
-- (<*>) :: f (a -> b) -> f a -> f b
<*>) (Just f) other = fmap f other
(<*>) Nothing _ = Nothing (
For Maybe, pure simply creates an instance of Just with the supplied value. With <*> the function f is within a Maybe context. If the context is a Just, the function is applied to the other Maybe context using fmap from the Functor typeclass. If the context is a Nothing, no function application takes place and a Nothing is returned.
How do we define an Applicative instance for (r ->) ?
instance Applicative (r -> ) where
-- pure :: a -> f a
pure a = \r -> a
-- (<*>) :: f (a -> b) -> f a -> f b
<*>) f g = \r -> f r (g r) -- f is (\r -> (a -> b)), g is (\r -> a) (
Apply the input r to g to return an a and also apply r to f, to return the function from (a -> b). Then apply the function (a -> b) to a to return a b.
Let’s use it on an example:
+) <$> (+3) <*> (*100) 5
(= (+) <$> (\r -> r + 3) <*> (\r -> r * 100) 5 -- expanding Applicative
= (+) <$> (5 + 3) (5 * 100) -- substituting 5 for 'r'
= 8 + 500 -- combining with (+)
= 508
You may also notice that this gives you the same answer as liftA2:
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
+) (+3) (*100) 5
liftA2 (= 508
The intuition here is that, we can supply the input to each Applicative context, and then combine them with a function either through <$> or liftA2.
And here’s one more example which may seem a little hairy:
-> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5
(\x y z = (\x y z -> [x,y,z]) <$> (\r -> r +3) <*> (\r -> *2) <*> (\r -> /2) $ 5 -- expand Applicative
= (\x y z -> [x,y,z]) <$> (5 + 3) <*> (5 * 2) <*> (5 / 2) -- replace 'r' with 5
= (\x y z -> [x,y,z]) <$> (8.0) <*> (10.0) <*> (2.5)
= [8.0, 10.0, 2.5] -- combine with (\x y z -> [x,y,z])
The same result can be achieved with liftA3:
-> [x,y,z]) (+3) (*2) (/2) $ 5
liftA3 (\x y z = [8.0,10.0,2.5]
Monad
The Monad typeclass is defined as:
class Applicative m => Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
The return function lifts a value a into the Monad m. Bind or (>>=) sequentially composes two actions, passing any value produced by the first as an argument to the second.
Let’s take a look at the Monad instance for Maybe :
instance Monad Maybe where
-- (>>=) :: m a -> (a -> m b) -> m b
Just a) >>= k = k a
(Nothing >>= _ = Nothing
If the first Maybe context is a Just, then apply the function k to produce a new Maybe context. If the first Maybe context is a Nothing, then return Nothing.
How do we define an Monad instance for (r ->) ?
instance Monad (r ->) where
-- return :: a -> m a
return = pure
-- (>>=) :: m a -> (a -> m b) -> m b
>>= g = \r -> g (f r) r -- f is (\r -> a), g is (\a -> \r -> b) f
The return function is derived from pure, since all Monads are also Applicatives. The bind function (>>=) first applies the input r to f to give an a. It then applies the a to g to return a function from (r -> b). The input r is then applied to this function to return the final b.
The intuition here is that we supply the input resource r to f and use that result as the first input to g followed by r as the second input.
Let’s use it in an example:
+3) >>= (*) $ 5
(= (\r -> r + 3) >>= (\a -> \r -> a * r) 5 -- expanding the Monad for 'r'
= (5 + 3) >>= (\a -> a * 5) -- replace 'r' with 5
= (8) >>= (\a -> a * 5)
= (8 * 5) -- replace 'a' with 8
= 40
or simply:
+3) >>= (*) $ 5
(= ((5+3) * 5)
= (8 * 5)
= 40
We can also use the do syntax to solve the above:
let z1 = do
<- (+3)
x *)
(x 5
z1 = 40
Join
The join function flattens nested Monads and is defined as:
join :: (Monad m) => m (m a) -> m a
= x >>= id join x
Given the following:
+) 10 join (
armed with the what we know about Monads, what is its result?
+) 10
join (-- m (m a) -> m a
= (\r -> (\r -> r + r)) 10 -- expanding the Monad for 'r'
= (10 + 10) -- replacing 'r' with 10
= 20
We can also use the do syntax to solve the above:
let z2 = do
<- (+)
x
x10
z2 = 20