Chapter 11 of Learn You a Haskell introduces the following definition:
instance Applicative ((->) r) where
pure x = (\\_ -> x)
f <*
Going through your original question, I think there's one subtle but very key point that you might have missed. Using the original example from LYAH:
(+) <$> (+3) <*> (*100) $ 5
This is the same as:
pure (+) <*> (+3) <*> (*100) $ 5
The key here is the pure
before (+)
, which has the effect of boxing (+)
as an Applicative. If you look at how pure
is defined, you can see that to unbox it, you need to provide an additional argument, which can be anything. Applying <*>
to (+) <$> (+3)
, we get
\x -> (pure (+)) x ((+3) x)
Notice in (pure (+)) x
, we are applying x
to pure
to unbox (+)
. So we now have
\x -> (+) ((+3) x)
Adding (*100)
to get (+) <$> (+3) <*> (*100)
and apply <*>
again, we get
\y -> (\x -> (+) ((+3) x)) y ((*100) y) {Since f <*> g = f x (g x)}
5 -> (\x -> (+) ((+3) x)) 5 ((*100) 5)
(\x -> (+) ((+3) x)) 5 (500)
5 -> (+) ((+3) 5) (500)
(+) 8 500
508
So in conclusion, the x
after f
is NOT the first argument to our binary operator, it is used to UNBOX the operator inside pure
.
“In the instance, substituting
((->)r)
forf
:r->(a->b)->(r->a)->(r->b)
”
Why, that's not right. It's actually (r->(a->b)) -> (r->a) -> (r->b)
, and that is the same as (r->a->b) -> (r->a) -> r -> b
. I.e., we map an infix and a function which returns the infix' right-hand argument, to a function which takes just the infix' LHS and returns its result. For example,
Prelude Control.Applicative> (:) <*> (\x -> [x]) $ 2
[2,2]
First of all, remember how fmap
is defined for applicatives:
fmap f x = pure f <*> x
This means that your example is the same as (fmap (+ 5) (* 3)) 4
. The fmap
function for functions is just composition, so your exact expression is the same as ((+ 5) . (* 3)) 4
.
Now, let's think about why the instance is written the way it is. What <*>
does is essentially apply a function in the functor to a value in the functor. Specializing to (->) r
, this means it applies a function returned by a function from r
to a value returned by a function from r
. A function that returns a function is just a function of two arguments. So the real question is this: how would you apply a function of two arguments (r
and a
, returning b
) to a value a
returned by a function from r
?
The first thing to note is that you have to return a value of type (->) r
which means the result also has to be a function from r
. For reference, here is the <*>
function:
f <*> g = \x -> f x (g x)
Since we want to return a function taking a value of type r
, x :: r
. The function we return has to have a type r -> b
. How can we get a value of type b
? Well, we have a function f :: r -> a -> b
. Since r
is going to be the argument of the result function, we get that for free. So now we have a function from a -> b
. So, as long as we have some value of type a
, we can get a value of type b
. But how do we get a value of type a
? Well, we have another function g :: r -> a
. So we can take our value of type r
(the parameter x
) and use it to get a value of type a
.
So the final idea is simple: we use the parameter to first get a value of type a
by plugging it into g
. The parameter has type r
, g
has type r -> a
, so we have an a
. Then, we plug both the parameter and the new value into f
. We need both because f
has a type r -> a -> b
. Once we plug both an r
and an a
in, we have a b1
. Since the parameter is in a lambda, the result has a type r -> b
, which is what we want.
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:
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.
(+) <$> (+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]