I don’t quite understand how functions behave when working with applicative functors. Here's an example:
ghci> (&&) <$> Just False <*> undefined
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
undefined, called at <interactive>:6:25 in interactive:Ghci6
But if we don't use the Maybe context, then everything works as expected:
ghci> (&&) False undefined
False
Accordingly, the question is - why does this happen and is it possible to somehow avoid it?
How to ensure such behavior that if (&&) <$> Just False
is encountered, the result of the function would immediately be Just False
?
The reason that the undefined
in (&&) <$> Just False <*> undefined
is evaluated is that the result depends on whether it is headed by a Just
constructor or a Nothing
:
> (&&) <$> Just False <*> Just False
Just False
> (&&) <$> Just False <*> Nothing
Nothing
If you want a function (&&^) :: Maybe Bool -> Maybe Bool -> Maybe Bool
such that Just False &&^ Nothing
evaluates to Just False
, then you need to make use of the monadic interface for Maybe
, not just the applicative one. That is:
(&&^) :: Monad m => m Bool -> m Bool -> m Bool
mx &&^ my = do
x <- mx
if x
then my
else return x
> Just False &&^ undefined
Just False
> Just True &&^ undefined
*** Exception: Prelude.undefined
((&&^)
is available in the extra
library.)
This illustrates the difference between the monadic and applicative interfaces: a monadic computation allows you to decide whether to run my
or not based on the result of running mx
, while an applicative computation requires you to specify all the computations in advance and then aggregate their results into one.
Compare this with the definition of liftA2 (&&)
(using ApplicativeDo
notation):
liftA2 (&&) :: Applicative f => f Bool -> f Bool -> f Bool
liftA2 (&&) fx fy = do
x <- fx
y <- fy
pure (x && y)