I am currently reading Learn You a Haskell for Great Good! and am stumbling on the explanation for the evaluation of a certain code block. I\'ve read the explanations severa
It is using the applicative instance for functions. Your code
(+) <$> (+3) <*> (*100) $ 5
is evaluated as
( (\a->b->a+b) <$> (\c->c+3) <*> (\d->d*100) ) 5 ( (\x -> (\a->b->a+b) ((\c->c+3) x)) <*> (\d->d*100) ) 5 ( (\x -> (\a->b->a+b) (x+3)) <*> (\d->d*100) ) 5 ( (\x -> b -> (x+3)+b) <*> (\d->d*100) ) 5 ( (\x->b->(x+3)+b) <*> (\d->d*100) ) 5 (\y -> ((\x->b->(x+3)+b) y) ((\d->d*100) y)) 5 (\y -> (b->(y+3)+b) (y*100)) 5 (\y -> (y+3)+(y*100)) 5 (5+3)+(5*100)
where <$>
is fmap
or just function composition .
, and <*>
is ap
if you know how it behaves on monads.
Let us first take a look how fmap and (<*>) are defined for a function:
instance Functor ((->) r) where fmap = (.) instance Applicative ((->) a) where pure = const (<*>) f g x = f x (g x) liftA2 q f g x = q (f x) (g x)
The expression we aim to evaluate is:
(+) <$> (+3) <*> (*100) $ 5
or more verbose:
((+) <$> (+3)) <*> (*100) $ 5
If we thus evaluate (<$>), which is an infix synonym for fmap
, we thus see that this is equal to:
(+) . (+3)
so that means our expression is equivalent to:
((+) . (+3)) <*> (*100) $ 5
Next we can apply the sequential application. Here f
is thus equal to (+) . (+3)
and g
is (*100)
. This thus means that we construct a function that looks like:
\x -> ((+) . (+3)) x ((*100) x)
We can now simplify this and rewrite this into:
\x -> ((+) (x+3)) ((*100) x)
and then rewrite it to:
\x -> (+) (x+3) ((*100) x)
We thus have constructed a function that looks like:
\x -> (x+3) + 100 * x
or simpler:
\x -> 101 * x + 3
If we then calculate:
(\x -> 101*x + 3) 5
then we of course obtain:
101 * 5 + 3
and thus:
505 + 3
which is the expected:
508
The other two answers have given the detail of how this is calculated - but I thought I might chime in with a more "intuitive" answer to explain how, without going through a detailed calculation, one can "see" that the result must be 508.
As you implied, every Applicative
(in fact, even every Functor
) can be viewed as a particular kind of "context" which holds values of a given type. As simple examples:
Maybe a
is a context in which a value of type a
might exist, but might not (usually the result of a computation which may fail for some reason)[a]
is a context which can hold zero or more values of type a
, with no upper limit on the number - representing all possible outcomes of a particular computationIO a
is a context in which a value of type a
is available as a result of interacting with "the outside world" in some way. (OK that one isn't so simple...)And, relevant to this example:
r -> a
is a context in which a value of type a
is available, but its particular value is not yet known, because it depends on some (as yet unknown) value of type r
.The Applicative
methods can be very well understood on the basis of values in such contexts. pure
embeds an "ordinary value" in a "default context" in which it behaves as closely as possible in that context to a "context-free" one. I won't go through this for each of the 4 examples above (most of them are very obvious), but I will note that for functions, pure = const
- that is, a "pure value" a
is represented by the function which always produces a
no matter what the source value.
Rather than dwell on how <*>
can best be described using the "context" metaphor though, I want to dwell on the particular expression:
f <$> a <*> b
where f
is a function between 2 "pure values" and a
and b
are "values in a context". This expression in fact has a synonym as a function: liftA2. Although using the liftA2
function is generally considered less idiomatic than the "applicative style" using <$>
and <*>
, the name emphasies that the idea is to "lift" a function on "ordinary values" to one on "values in a context". And when thought of like this, I think it is usually very intuitive what this does, given a particular "context" (ie. a particular Applicative
instance).
So the expression:
(+) <$> a <*> b
for values a
and b
of type say f Int
for an Applicative f
, behaves as follows for different instances f
:
f = Maybe
, then the result, if a
and b
are both Just
values, is to add up the underlying values and wrap them in a Just
. If either a
or b
is Nothing
, then the whole expression is Nothing
.f = []
(the list instance) then the above expression is a list containing all sums of the form a' + b'
where a'
is in a
and b'
is in b
.f = IO
, then the above expression is an IO action that performs all the I/O effects of a
followed by those of b
, and results in the sum of the Int
s produced by those two actions.So what, finally, does it do if f
is the function instance? Since a
and b
are both functions describing how to get a given Int
given an arbitrary (Int
) input, it is natural that lifting the (+)
function over them should be the function that, given an input, gets the result of both the a
and b
functions, and then adds the results.
And that is, of course, what it does - and the explicit route by which it does that has been very ably mapped out by the other answers. But the reason why it works out like that - indeed, the very reason we have the instance that f <*> g = \x -> f x (g x)
, which might otherwise seem rather arbitrary (although in actual fact it's one of the very few things, if not the only thing, that will type-check), is so that the instance matches the semantics of "values which depend on some as-yet-unknown other value, according to the given function". And in general, I would say it's often better to think "at a high level" like this than to be forced to go down to the low-level details of exactly how computations are performed. (Although I certainly don't want to downplay the importance of also being able to do the latter.)
[Actually, from a philosophical point of view, it might be more accurate to say that the definition is as it is just because it's the "natural" definition that type-checks, and that it's just happy coincidence that the instance then takes on such a nice "meaning". Mathematics is of course full of just such happy "coincidences" which turn out to have very deep reasons behind them.]
For any applicative,
a <$> b <*> c = liftA2 a b c
For functions,
liftA2 a b c x
= a (b x) (c x) -- by definition;
= (a . b) x (c x)
= ((a <$> b) <*> c) x
Thus
(+) <$> (+3) <*> (*100) $ 5
=
liftA2 (+) (+3) (*100) 5
=
(+) ((+3) 5) ((*100) 5)
=
(5+3) + (5*100)
(the long version of this answer follows.)
Pure math has no time. Pure Haskell has no time. Speaking in verbs ("applicative functor applies" etc.) can be confusing ("applies... when?...").
Instead, (<*>)
is a combinator which combines a "computation" (denoted by an applicative functor) carrying a function (in some context) and a "computation" of the same type, carrying a value (in like context), into one combined "computation" that carries out the application of that function to that value (in such context).
"Computation" is used to contrast it with a pure Haskell "calculations". "Computations" might or might not be pure themselves, that's an orthogonal issue. But mainly what it means, is that "computations" embody a generalized function call protocol. They might "do" something in addition to / as part of / carrying out the application of a function to its argument. Or in types,
( $ ) :: (a -> b) -> a -> b
(<$>) :: (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b
(=<<) :: (a -> f b) -> f a -> f b
With functions, the context is application (another one), and to recover the value -- be it a function or an argument -- the application to a common argument is performed.
(bear with me, we're almost there). The pattern a <$> b <*> c
is also expressible as liftA2 a b c
. And so, the "functions" applicative functor "computation" type is defined by
liftA2 h x y s = let x' = x s -- embellished application of h to x and y
y' = y s in -- in context of functions, or Reader
h x' y'
-- liftA2 h x y = let x' = x -- non-embellished application, or Identity
-- y' = y in
-- h x' y'
-- liftA2 h x y s = let (x',s') = x s -- embellished application of h to x and y
-- (y',s'') = y s' in -- in context of
-- (h x' y', s'') -- state-passing computations, or State
-- liftA2 h x y = let (x',w) = x -- embellished application of h to x and y
-- (y',w') = y in -- in context of
-- (h x' y', w++w') -- logging computations, or Writer
-- liftA2 h x y = [h x' y' | -- embellished application of h to x and y
-- x' <- x, -- in context of
-- y' <- y ] -- nondeterministic computations, or List
-- ( and for Monads we define `liftBind h x k =` and replace `y` with `k x'`
-- in the bodies of the above combinators; then liftA2 becomes liftBind: )
-- liftA2 :: (a -> b -> c) -> f a -> f b -> f c
-- liftBind :: (a -> b -> c) -> f a -> (a -> f b) -> f c
-- (>>=) = liftBind (\a b -> b) :: f a -> (a -> f b) -> f b
Indeed,
> :t let liftA2 h x y r = h (x r) (y r) in liftA2
:: (a -> b -> c) -> (t -> a) -> (t -> b) -> (t -> c)
> :t liftA2 -- the built-in one
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
i.e. the types match when we take f a ~ (t -> a) ~ (->) t a
, i.e. f ~ (->) t
.
And so, we're already there:
(+) <$> (+3) <*> (*100) $ 5
=
liftA2 (+) (+3) (*100) 5
=
(+) ((+3) 5) ((*100) 5)
=
(+) (5+3) (5*100)
=
(5+3) + (5*100)
It's just how liftA2
is defined for this type, Applicative ((->) t) => ...
:
instance Applicative ((->) t) where
pure x t = x
liftA2 h x y t = h (x t) (y t)
There's no need to define (<*>)
. The source code says:
Minimal complete definition
pure, ((<*>) | liftA2)
So now you've been wanting to ask for a long time, why is it that a <$> b <*> c
is equivalent to liftA2 a b c
?
The short answer is, it just is. One can be defined in terms of the other -- i.e. (<*>)
can be defined via liftA2
,
g <*> x = liftA2 id g x -- i.e. (<*>) = liftA2 id = liftA2 ($)
-- (g <*> x) t = liftA2 id g x t
-- = id (g t) (x t)
-- = (id . g) t (x t) -- = (id <$> g <*> x) t
-- = g t (x t)
(which is exactly as it is defined in the source),
and it is a law that every Applicative Functor must follow, that h <$> g = pure h <*> g
.
Lastly,
liftA2 h g x == pure h <*> g <*> x
-- h g x == (h g) x
because <*>
associates to the left: it is infixl 4 <*>
.