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.
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,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:
This is the same as:
The key here is the
pure
before(+)
, which has the effect of boxing(+)
as an Applicative. If you look at howpure
is defined, you can see that to unbox it, you need to provide an additional argument, which can be anything. Applying<*>
to(+) <$> (+3)
, we getNotice in
(pure (+)) x
, we are applyingx
topure
to unbox(+)
. So we now haveAdding
(*100)
to get(+) <$> (+3) <*> (*100)
and apply<*>
again, we getSo in conclusion, the
x
afterf
is NOT the first argument to our binary operator, it is used to UNBOX the operator insidepure
.First of all, remember how
fmap
is defined for applicatives:This means that your example is the same as
(fmap (+ 5) (* 3)) 4
. Thefmap
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 fromr
to a value returned by a function fromr
. 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
anda
, returningb
) to a valuea
returned by a function fromr
?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 fromr
. For reference, here is the<*>
function:Since we want to return a function taking a value of type
r
,x :: r
. The function we return has to have a typer -> b
. How can we get a value of typeb
? Well, we have a functionf :: r -> a -> b
. Sincer
is going to be the argument of the result function, we get that for free. So now we have a function froma -> b
. So, as long as we have some value of typea
, we can get a value of typeb
. But how do we get a value of typea
? Well, we have another functiong :: r -> a
. So we can take our value of typer
(the parameterx
) and use it to get a value of typea
.So the final idea is simple: we use the parameter to first get a value of type
a
by plugging it intog
. The parameter has typer
,g
has typer -> a
, so we have ana
. Then, we plug both the parameter and the new value intof
. We need both becausef
has a typer -> a -> b
. Once we plug both anr
and ana
in, we have ab1
. Since the parameter is in a lambda, the result has a typer -> b
, which is what we want.