1
addRecip :: Double -> Double -> Maybe Double
addRecip x y = fmap (+) (recipMay x) <*> recipMay y
  where
    recipMay a | a == 0 = Nothing
               | otherwise = Just (1 / a)

I look up some explanation for <*>.

<*> takes a functor that contains a function taking an a and returning a b, and a functor that contains an a, and it returns a functor that contains a b. So <*> kind of extract the function from a functor and applies it to an arguments also inside a functor, and finally returns the result into a functor

This is an example:

fs <*> xs = [f x | f <- fs, x <- xs]

But in my case, it seems a bit different. The elements in recipMay x are not functions.

user8314628
  • 1,952
  • 2
  • 22
  • 46

3 Answers3

3

<*> Applies an applicative value to another. It's a richer counterpart to regular function application. The applicative values are decorated in some way, for example, it can be optional whether there's any value as you would perceive it (for Maybe, which is your case), or there can be very many values (for List).

The application of one applicative value to the other therefore has some special behaviour. For lists, a <*> b applies each member of a to each member of b making a huge list of all combinations whilst for Maybe (which is your case) a <*> b gives Just (a' b') if a and b are (Just a') and (Just b') and gives Nothing if either or both a and b are Nothing - for Maybe, in summary, it's function application for optional values where the result is absent if any value involved is absent.

There are some rules to how <*> is implemented which means that you can always view this as [apply a "contained function" to a "contained value"] and as long as you do all your work in the contained domain (using <$>, <*>, pure, >>=, <|>, etc) then you can think of it as the same as regular function application, but when you come to "extract" values you get to see the added richness.

codeshot
  • 1,183
  • 1
  • 9
  • 20
3

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 bs. The result is then a collection (the same type of collection) of bs, 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 Justs (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.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • "For a list" ..."applying every element in the second collection to every function in the first collection.” The list comprehension is Cartesian, each fs with every xs. Should it be a zip fs xs? Ambiguous. – fp_mora Aug 19 '18 at 22:17
  • 1
    @fp_mora: well there exists a type for such kind of `<*>`: the `ZipList` http://hackage.haskell.org/package/base-4.11.1.0/docs/Control-Applicative.html#t:ZipList :) – Willem Van Onsem Aug 19 '18 at 22:22
  • Totally impressive and of great use. TY! – fp_mora Aug 19 '18 at 23:20
2

Here the functor is Maybe. That <*> will return Nothing if either argument is Nothing (i.e., it involved a division by zero)

Nothing <*> _       = Nothing
_       <*> Nothing = Nothing

In the remaining case, it just applies the wrapped function:

Just f <*> Just x = Just (f x)

Also note that

fmap (+) (recipMay x) <*> recipMay y

is a slightly unusual notation. Usually that's written as

(+) <$> recipMay x <*> recipMay y

which is completely equivalent, since fmap is written as the infix <$>, but arguably more readable.

Here, fmap (+) (recipMay x) (or (+) <$> recipMay x) means

if x == 0
then Nothing 
else Just (\a -> 1/x + a)
chi
  • 111,837
  • 3
  • 133
  • 218