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.
In Haskell, an applicative is a parametrized type that we think of as being a container for data of that type plus two methods pure and <*> . Consider a parametrized type f a . The pure method for an applicative of type f has type. pure :: a -> f a. and can be thought of as bringing values into the applicative.
A function assigns to every element of a set X an element of a set Y. A functor assigns to every object of a category C an object of a category D and also assigns to every morphism in C a morphism in D in a way compatible with sources, targets, and composition.
Functor in Haskell is a kind of functional representation of different Types which can be mapped over. It is a high level concept of implementing polymorphism. According to Haskell developers, all the Types such as List, Map, Tree, etc. are the instance of the Haskell Functor.
A monad is a way of composing functions that require context in addition to the return value, such as computation, branching, or I/O. Monads type lift, flatten and map so that the types line up for lifting functions a => M(b) , making them composable.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With