14

I am a beginner with haskell and am reading the Learn you a haskell book. I have been trying to digest functors and applicative functors for a while now.

In the applicative functors topic, the instance implementation for Maybe is given as

instance Applicative Maybe where
  pure = Just
  Nothing <*> _ = Nothing
  (Just f) <*> something = fmap f something

So, as I understand it, we get Nothing if the left side functor (for <*>) is Nothing. To me, it seems to make more sense as

  Nothing <*> something = something

So that this applicative functor has no effect. What is the usecase, if any for giving out Nothing?

Say, I have a Maybe String with me, whose value I don't know. I have to give this Maybe to a third party function, but want its result to go through a few Maybe (a -> b)'s first. If some of these functions are Nothing I'll want them to silently return their input, not give out a Nothing, which is loss of data.

So, what is the thinking behind returning Nothing in the above instance?

sharat87
  • 7,330
  • 12
  • 55
  • 80

4 Answers4

15

How would that work? Here's the type signature:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

So the second argument here would be of type Maybe a, while the result needs to be of type Maybe b. You need some way to turn a into b, which you can only do if the first argument isn't Nothing.

The only way something like this would work is if you have one or more values of type Maybe (a -> a) and want to apply any that aren't Nothing. But that's much too specific for the general definition of (<*>).


Edit: Since it seems to be the Maybe (a -> a) scenario you actually care about, here's a couple examples of what you can do with a bunch of values of that type:

Keeping all the functions and discard the Nothings, then apply them:

applyJust :: [Maybe (a -> a)] -> a -> a
applyJust = foldr (.) id . catMaybes

The catMaybes function gives you a list containing only the Just values, then the foldr composes them all together, starting from the identity function (which is what you'll get if there are no functions to apply).

Alternatively, you can take functions until finding a Nothing, then bail out:

applyWhileJust :: [Maybe (a -> a)] -> a -> a
applyWhileJust (Just f:fs) = f . applyWhileJust fs
applyWhileJust (Nothing:_) = id

This uses a similar idea as the above, except that when it finds Nothing it ignores the rest of the list. If you like, you can also write it as applyWhileJust = foldr (maybe (const id) (.)) id but that's a little harder to read...

C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • Ah yes. The `Maybe (a -> a)` case was what I had in mind. So, as you said, this is too specific for the general definition of `<*>`. Then, how should the above case addressed (return `something` instead of `Nothing`)? Should I implement a `Applicative'`? – sharat87 Dec 25 '11 at 03:55
  • 2
    Probably not useful enough. Why not just put the `Maybe (a -> a)` values in a list, then write a function that finds the non-`Nothing` functions and composes them? e.g. `applyJust :: [Maybe (a -> a)] -> a -> a`. – C. A. McCann Dec 25 '11 at 04:00
  • Thanks McCann. Using a function like `applyJust` seems to address the `Maybe (a -> a)` case. I understand the `<*>` is implemented to be more general in nature. I feel I have a grip now :) – sharat87 Dec 25 '11 at 04:29
  • @ShrikantSharat: Hopefully the two examples I edited into my answer will give you enough to get started. :] – C. A. McCann Dec 25 '11 at 04:32
8

Think of the <*> as the normal * operator. a * 0 == 0, right? It doesn't matter what a is. So using the same logic, Just (const a) <*> Nothing == Nothing. The Applicative laws dictate that a data type has to behave like this.

The reason why this is useful, is that Maybe is supposed to represent the presence of something, not the absence of something. If you pipeline a Maybe value through a chain of functions, if one function fails, it means that a failure happened, and that the process needs to be aborted.

The behavior you propose is impractical, because there are numerous problems with it:

  1. If a failed function is to return its input, it has to have type a -> a, because the returned value and the input value have to have the same type for them to be interchangeable depending on the outcome of the function
  2. According to your logic, what happens if you have Just (const 2) <*> Just 5? How can the behavior in this case be made consistent with the Nothing case?

See also the Applicative laws.

EDIT: fixed code typos, and again

dflemstr
  • 25,947
  • 5
  • 70
  • 105
  • Thank you dflemstr. The analogy you gave makes a lot of sense to the intention and the way the function `<*>` is written :). I'll read the laws you gave. – sharat87 Dec 25 '11 at 04:31
  • "The Applicative laws dictate that a data type has to behave like this." Actually they don't, even the Alternative laws don't dictate that, neither do the MonadPlus laws (MonadPlus laws only dictate that `mzero <*> a == mzero`, but not the other way around). I mean Applicative doesn't even have a concept of "0", only a concept of "1" (pure). – semicolon Jun 16 '17 at 19:16
3

Well what about this?

Just id <*> Just something

The usecase for Nothing comes when you start using <*> to plumb through functions with multiple inputs.

(-) <$> readInt "foo" <*> readInt "3"

Assuming you have a function readInt :: String -> Maybe Int, this will turn into:

(-) <$> Nothing <*> Just 3

<$> is just fmap, and fmap f Nothing is Nothing, so it reduces to:

Nothing <*> Just 3

Can you see now why this should produce Nothing? The original meaning of the expression was to subtract two numbers, but since we failed to produce a partially-applied function after the first input, we need to propagate that failure instead of just making up a nice function that has nothing to do with subtraction.

Dan Burton
  • 53,238
  • 27
  • 117
  • 198
  • Yes, put this way, it makes sense. What I imagined initially was that `<*>`'s intention was to allow a functor (`Just 3` in this case) to be sort of *piped* through various functions (`Just (a->b)`'s actually). So, if some of those were to be `Nothing`'s, I thought they should *not* propagate. More practical scenarios seem to want it to propagate. Thanks for the answer! – sharat87 Dec 25 '11 at 11:49
1

Additional to C. A. McCann's excellent answer I'd like to point out that this might be a case of a "theorem for free", see http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf . The gist of this paper is that for some polymorphic functions there is only one possible implementiation for a given type signature, e.g. fst :: (a,b) -> a has no other choice than returning the first element of the pair (or be undefined), and this can be proven. This property may seem counter-intuitive but is rooted in the very limited information a function has about its polymorphic arguments (especially it can't create one out of thin air).

Landei
  • 54,104
  • 13
  • 100
  • 195
  • It doesn't quite work here: an implementation that ignored both inputs and returned `Nothing` would also have the correct type. That may be the only other possibility, however. – C. A. McCann Dec 25 '11 at 18:47