haskell

How laziness works with applicative functors in Haskell?


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?


Solution

  • 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)