Functor for function

Let’s first look at following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
import Control.Applicative

mulFunc :: Integer -> Integer
mulFunc x = x * 2

addFunc :: Integer -> Integer
addFunc x = x + 10

m :: Integer -> Integer
m = mulFunc . addFunc

m' :: Integer -> Integer
m' = fmap mulFunc addFunc

It is going to fmap a function over another function where the functorial context is a partially applied function. The functorial context means the structure that the function is being lifted over in order to apply to the value inside. For example, a list is a functorial context we can lift functions over. We say that the function gets lifted over the structure of the list and applied to or mapped over the values that are inside the list.

As in function composition, fmap composes the two functions before applying them to the argument. The result of the one can then get passed to the next as input. Using fmap here lifts the one partially-applied function over the next. This is the Functor of functions.

The implementation of the instance of the functor for function is like:

1
2
3
4
5
6
7
8
9
instance Functor ((->) r) where
  fmap = (.)

-- the function type
data (->) a b

-- and
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \a -> f (g a)

From this, we can determine that r, the argument type for functions, is part of the structure being lifted over when we lift over a function, not the value being transformed or mapped over.

This leaves the result of the function as the value being transformed.

1
2
3
4
5
6
7
8
9
10
11
instance Functor ((->) r) where
  fmap = (.)

-- the function type
(.) :: (b -> c) -> (a -> b) -> (a -> c)

fmap :: Functor f => 
        (a -> b) -> f a -> f b
     :: (b -> c) -> f b -> f c
     :: (b -> c) -> ((->) a) b -> ((->) a) c
     :: (b -> c) -> (a -> b) -> (a -> c)

That is functorial lifting for functions.

Applicative for function

Following is an exmaple for applicative for functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
import Control.Applicative

mulFunc :: Integer -> Integer
mulFunc x = x * 2

addFunc :: Integer -> Integer
addFunc x = x + 10

m2 :: Integer -> Integer
m2 = (+) <$> mulFunc <*> addFunc

m2' :: Integer -> Integer
m2' = liftA2 (+) mulFunc addFunc

Now we’re in an applicative context. We’ve added another function to lift over the contexts of our partially-applied functions. This time, we still have partially-applied functions that are awaiting application to an argument, but this will work differently than fmapping did. This time, the argument will get passed to both functions in parallel, and the results will be added together.

Mapping a function awaiting two arguments over a function awaiting one produces a two argument function.

1
2
3
(*2) :: Num a => a -> a
(+) :: Num a => a -> a -> a
(+) <$> (*2) :: Num a => a -> a -> a

We’d use applicative for functions when two functions would share the same input and we want to apply some other function to the result of those to reach a final result.

1
2
3
(<*>) :: Applicative f => (f   (a -> b)) -> (f    a) -> (f    b)
--                        f ~ a ->          f ~ a ->    f ~ a ->
(<*>) ::                 (a -> (a -> b)) -> (a -> a) -> (a -> b)

The implementation of applicative for function is like:

1
2
3
4
5
6
7
instance Applicative ((->) r) where
  pure = const
  f <*> a = \r -> f r (a r)

-- where
const :: a -> b -> a
const a b = a

Monad for function

Following is an example for monad for functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
import Control.Applicative

mulFunc :: Integer -> Integer
mulFunc x = x * 2

addFunc :: Integer -> Integer
addFunc x = x + 10

m3 :: Integer -> Integer
m3 = do
  a <- mulFunc
  b <- addFunc
  return (a + b)

This time the context is monadic, it does the same thing as the example in applicative for functions. We assign the variable a to the partially-applied function mulFunc, and b to addFunc. As soon as we receive an input, it will fill the empty slots in mulFunc and addFunc. The results will be bound to the variables a and b and passed into return.

This is the idea of Reader, which will be described below soon. It is a way of stringing functions together when all those functions are awaiting one input from a shared environment.

It is just another way of abstracting out function application and gives us a way to do computation in terms of an argument that hasn’t been supplied yet. We use this most often when we have a constant value that we will obtain from somewhere outside our program that will be an argument to a whole bunch of functions. Using Reader allows us to avoid passing that argument around explicitly.

The implementation of monad for function is like:

1
2
3
4
5
6
7
instance Monad ((->) r) where
  return = pure
  m >>= k = flip k <*> m

-- where
flip :: (a -> b -> c) -> (b -> a -> c)
flip f a b = f b a

Speaking generally in terms of the algebras alone, you cannot get a Monad instance from the Applicative. You can get an Applicative from the Monad. However, our instances above aren’t in terms of an abstract datatype – we know it’s the type of functions.

Because it’s not hiding behind a Reader newtype, we can use flip and apply to make the Monad instance. We need specific type information to augment what the Applicative is capable of before we can get our Monad instance.

Reader

As we saw above, functions have Functor, Applicative, and Monad instances. Usually when you see or hear the term Reader, it’ll be referring to the Monad or Applicative instances.

We use function composition because it lets us compose two functions without explicitly having to recognize the argument that will eventually arrive; the Functor of functions is function composition. With the Functor of functions, we are able to map an ordinary function over another to create a new function awaiting a final argument.

The Applicative and Monad instances for the function type give us a way to map a function that is awaiting an a over another function that is also awaiting an a. Reader means reading an argument from the environment into functions.

Reader is defined as follows:

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

The r is the type we’re “reading” in and a is the result type of the function. The Reader newtype has a handy runReader accessor to get the function out of Reader.

Functor instance for Reader

1
2
3
4
5
6
7
8
9
10
11
12
instance Functor (Reader r) where
  fmap :: (a -> b) -> Reader r a -> Reader r b
  fmap f (Reader ra) = Reader $ \r -> f (ra r)
                     = Reader $ (f . ra)

-- same as (.)
compose :: (b -> c) -> (a -> b) -> (a -> c)
compose f g = \x -> f (g x)

-- where
\r -> f (ra r)
\x -> f (g x)

Applicative instance for Reader

To write the Applicative instance for Reader, need to use a pragma called InstanceSigs. It’s an extension to assert a type for the typeclass methods. It is ordinarily not allowed to assert type signatures in instances. The compiler already knows the type of the functions, so it’s not usually necessary to assert the types in instances anyway. Here is for the sake of clarity, to make the Reader type explicit in our signatures.

1
2
3
4
5
6
7
{-# LANGUAGE InstanceSigs #-}
instance Applicative (Reader r) where
  pure :: a -> Reader r a
  pure a = Reader $ \_ -> a = Reader . const
  
  (<*>) :: Reader r (a -> b) -> Reader r a -> Reader r b
  (Reader rab) <*> (Reader ra) = Reader $ \r -> (rab r) (ra r)

Monad instance for Reader

1
2
3
4
5
6
7
8
9
10
11
12
{-# LANGUAGE InstanceSigs #-}
instance Monad (Reader r) where
  return :: a -> Reader r a
  return = pure = Reader . const

  (>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b
        :: forall a b. Reader r a -> (a -> Reader r b) -> Reader r b
  (Reader ra) >>= aRb = Reader $ \r -> runReader (aRb (runReader ra r)) r

-- and
ask :: Reader r r
ask = Reader $ \r -> r = Reader id

Reader Monad by itself is kinda boring. It can’t do anything the Applicative cannot.

Change Reader context

The following code lets us start a new Reader context with a different argument being provided:

1
2
3
4
5
6
7
withReaderT 
    :: (r' -> r)
    -- ^ The function to modify the environment.
    -> ReaderT r m a
    -- ^ Computation to run in the modified environment.
    -> ReaderT r' m a
withReaderT f m = ReaderT $ runReaderT m . f