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?
Maybe is also an applicative functor, but more exist. The next article will give you another example. Next: Applicative validation.
Monads are not a replacement for applicative functors Instead, every monad is an applicative functor (as well as a functor). It is considered good practice not to use >>= if all you need is <*>, or even fmap.
In Haskell, an applicative is a parametrized type that we think of as being a container for data of that type plus two methods pure and <*> . Consider a parametrized type f a . The pure method for an applicative of type f has type. pure :: a -> f a. and can be thought of as bringing values into the applicative.
The function pure is the second method required by the Applicative type class. The pure method is a useful helper function for taking an ordinary value or function and putting it into a context. The best way to understand pure is to play around with it in GHCi.
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 Nothing
s, 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...
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:
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 functionJust (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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With