The (<*>) :: Applicative f => f (a -> b) -> f a -> f b
comes from the Applicative
typeclass. An Applicative
is a (quoting the documentation) "A functor with application.". You can think of a Functor
as a collection (although there are other types that are no collections that are functors, like a function for example).
If we see a functor as a collection then the (<*>)
operator thus takes two of these collections. The first collection stores functions of type a -> b
, and the latter is a collection of b
s. The result is then a collection (the same type of collection) of b
s, by applying every element in the second collection to every function in the first collection.
So for a list it looks like:
(<*>) :: [a -> b] -> [a] -> [b]
(<*>) fs xs = [fi xj | fi <- fs, xj <- xs]
A Maybe
is also some sort of collection: it either contains no elements (the Nothing
case), or one element (the Just x
case with x
the element). You can thus see a Maybe
as a collection with "multiplicity" 0..1.
In case one of the two operands is a Nothing
(or both), then the result is a Nothing
as well, since if there is no function, or no element, there is no "result" of a function application. Only in case both operands are Just
s (so Just f
and Just x
), we can perform function application (so Just (f x)
):
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
(<*>) (Just f) (Just x) = Just (f x)
(<*>) _ _ = Nothing
In this specific case, we can analyze the use:
addRecip :: Double -> Double -> Maybe Double
addRecip x y = (fmap (+) (recipMay x)) <*> recipMay y
where
recipMay a | a == 0 = Nothing
| otherwise = Just (1 / a)
We thus see two operands: fmap (+) (RecipMay x)
and recipMay y
. In case x
and/or y
are 0
, then the operands are respectively Nothing
. Since in that case the corresponding recipMay
is Nothing
.
We thus could write it like:
addRecip :: Double -> Double -> Maybe Double
addRecip x y | x == 0 = Nothing
| y == 0 = Nothing
| otherwise = Just ((1/x) + (1/y))
But in the above we thus repeat the == 0
, and 1/
logic twice.