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 typea
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 typea
, with no upper limit on the number – representing all possible outcomes of a particular computationIO a
is a context in which a value of typea
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 typea
is available, but its particular value is not yet known, because it depends on some (as yet unknown) value of typer
.
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
:
- if
f = Maybe
, then the result, ifa
andb
are bothJust
values, is to add up the underlying values and wrap them in aJust
. If eithera
orb
isNothing
, then the whole expression isNothing
. - if
f = []
(the list instance) then the above expression is a list containing all sums of the forma' + b'
wherea'
is ina
andb'
is inb
. - if
f = IO
, then the above expression is an IO action that performs all the I/O effects ofa
followed by those ofb
, and results in the sum of theInt
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.]