From the chapter on Functors in Learn You a Haskell for Great Good, Lipovača states:
"When we do
(+) <$> (+3) <*> (*100)
, we're making a function that will use+
on the results of(+3)
and(*100)
and return that. To demonstrate on a real example, when we did(+) <$> (+3) <*> (*100) $ 5
, the5
first got applied to(+3)
and(*100)
, resulting in8
and500
. Then,+
gets called with8
and500
, resulting in508
."
However, if I try to evaluate the function myself, considering this definition for Applicative on the functor ((->) r):
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
I read the evaluation of the above expression as:
(\x -> (3 + x) (100 * x)) $ 5
But I don't see how we can compose two partially applied binary functions as a single lambda (in fact, GHCi throws an infinite type error trying to bind this to a variable). Furthermore, to a working interpretation, if we look at the type definition for <$>
we get:
(<$>) :: Functor f => (a -> b) -> f a -> f b
or more specifically we can look at its lifting as:
(<$>) :: Functor f => (a -> b) -> (f a -> f b)
Considering that our functor in this case is ((->) r), I can deduce that this is what transformation takes place on the previous evaluation (assuming that left associativity happens first, instead of the right associative application of 5
):
(\x -> a + b)
where a
= (+ 3)
and b
= (* 100)
. This is the function that should be returned. However, am I correct in assuming that this is the final (rough) form?
(\x -> (3 + x) + (100 * x)) $ 5
...which yields 508.
I find Lipovača's description more comprehensible in terms of how the expression works, but my gut tells me isn't entirely true for the gorey details under the Haskell compiler hood. It is easier for me to think that the fmap of (+) happened first resulting in a function with two functors who are partially applied functions that take a shared input, and then we applied a value to it. We can do this because of lazy evaluation. Is this wrong?