functions as applicative functors (Haskell / LYAH)

Viewed 5537

Chapter 11 of Learn You a Haskell introduces the following definition:

instance Applicative ((->) r) where
    pure x = (\_ -> x)
    f <*> g = \x -> f x (g x)

Here, the author engages in some uncharacteristic hand-waving ("The instance implementation for <*> is a bit cryptic, so it's best if we just [show it in action without explaining it]"). I'm hoping someone here might help me figure it out.

According to the applicative class definition, (<*>) :: f (a -> b) -> f a -> f b

In the instance, substituting ((->)r) for f: r->(a->b)->(r->a)->(r->b)

So the first question, is how do I get from that type to f <*> g = \x -> f x (g x)?

But even if I take that last formula for granted, I have trouble making it agree with examples I give to GHCi. For example:

Prelude Control.Applicative> (pure (+5)) <*> (*3) $ 4
17

This expression instead appears consistent with f <*> g = \x -> f (g x) (note that in this version x doesn't appear after f.

I realize this is messy, so thanks for bearing with me.

4 Answers

How to understand Function as Functor and Function as Applicative?

First, how to understand function as functor?

We can regard functor as an empty box such as:

instance Functor Maybe where 
    fmap :: (a -> b) -> f a -> f b
    fmap f (Just x) = Just (f x) 
    fmap f Nothing = Nothing

there, Maybe type can be seen as an empty box with one slot which take a type to generate a concrete type Maybe a. In the fmap function:

  • The first parameter is a function, which maps from a to b;
  • The second parameter is a value of the type with the slot filled(concrete type), this concrete type is generated by type constructor and has the type f a (f is Maybe, so f a is Maybe a ).

When we implement function functors, for function functors must have two parameters to make a type a -> b, if we want our function functor has exactly one slot, we should first fill a slot, so the type constructor of function functor is ((->) r):

instance Functor ((->) r) where 
    fmap f g = (\x -> f (g x))

As the same as the fmap function in Maybe Functor, we should regard the second parameter g as a value of a concrete type which is generate by f (f equals (->) r), so f a is (->) r a which can be seen as r -> a. Finally, it is not difficult to understand that the g x in the fmap function cannot be seen as r -> x, it is just a function application which can be seen as (r -> a) x, also (x -> a).

Finally, it is not hard to understand that the <*> function in Applicative function (->) r can be implemented as following:

<*> :: f (a -> b) -> f a -> f b
<*> :: (r -> a -> b) -> (r -> a) -> (r -> b)
<&> :: (a -> b) -> (r -> a) -> (r -> b)
f <*> g = \r -> f r (g r)

for g r will map r to a, f r a will map r, a to b, so the whole lambda function can be seen as r -> b, also f b. For an instance:

((+) <*> (+3)) 5

the result is 5 + (5 + 3) = 13.

How to understand in functions as applicatives, (+) <$> (+3) <*> (*100) $ 5 = 508?

We know (+) has type: Num a, a -> a -> a;

We also know (+3) and (*100) has type: Num r, a, r -> a;

(+) <$> (+3) equals pure (+) <*> (+3), where :t pure (+) equals Num _, a, _ -> a -> a -> a

In another words, the pure (+) simply takes a _ parameter whatever and return the + operator, the parameter _ has no effect on the final return value. pure (+) also maps the return value of function (+3) to a function. Now for

f <*> g = \r -> f r (g r)

we can apply the operators and get:

pure (+) <*> (+3) = 
    \r -> f r (gr) =
    \r -> + (gr) =
    \r -> + (r + 3) =
    \r x -> x + (r + 3)

it has the type r -> x -> a. We then calculate pure (+) <*> (+3) <*> (*100) using the definition of <*>, and get:

pure (+) <*> (+3) <*> (*100) = 
    \r -> f r (gr) =
    \r -> (r + 3) + (gr)
    \r -> (r + 3) + (r * 100)

then we apply this function with parameter 5, we get:

(5 + 3) + (5 * 100) = 508 

we can simply think this applicative style as first to calculate the value after <$> and sum them up with the operator before <$>. In last example, this operator is a binary operator equals (+), we can replace it with a triple operator (\x y z -> [x,y,z]), so the following equation holds:

(\x y z -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5 = [8.0,10.0,2.5]
Related