简体   繁体   中英

Why is liftA2 added to Applicative as a method?

I came across this discussion on the Haskell mailing list. From the discussion, there seems to be performance implications with adding liftA2 as a method of Applicative. Can you provide concrete examples why it is necessary to add liftA2 to Applicative methods?

The email is written in 2017. At that time the Applicative typeclass looked like:

 class Functor f => Applicative f where -- | Lift a value. pure:: a -> fa -- | Sequential application. (<*>):: f (a -> b) -> fa -> fb -- | Sequence actions, discarding the value of the first argument. (*>):: fa -> fb -> fb a1 *> a2 = (id <$ a1) <*> a2 -- This is essentially the same as liftA2 (const id), but if the -- Functor instance has an optimized (<$), we want to use that instead. -- | Sequence actions, discarding the value of the second argument. (<*):: fa -> fb -> fa (<*) = liftA2 const

So without the liftA2 as part of the Applicative typeclass. It was defined as [src] :

 liftA2:: Applicative f => (a -> b -> c) -> fa -> fb -> f c liftA2 fab = fmap fa <*> b

so one could not make a special implementation in the typeclass. This means that sometimes liftA2 can be implemented more efficiently, but one can not define that.

For example the Maybe functor and Applicative are implemented as:

instance Functor Maybe where
    fmap f (Just x) = Just (f x)
    fmap _ Nothing = Nothing

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

This thus means that the liftA2 for a Maybe is implemented similar to:

liftA2Maybe :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
liftA2Maybe f x y = apMaybe (fmapMaybe f x) y
    where fmapMaybe f (Just x) = Just (f x)
          fmapMaybe _ Nothing = Nothing
          apMaybe (Just f) (Just x) = Just (f x)
          apMaybe _ _ = Nothing

But this is not optimal. It means that fmapMaybe will inspect if the parameter is a Just x , or Nothing , and then return a Just (fx) or a Nothing . But regardless, apMaybe will again inspect that, whereas we can already know that in advance. We can make a more efficient implementation with:

liftA2Maybe :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
liftA2Maybe f (Just x) (Just y) = Just (f x y)
liftA2Maybe _ _ _ = Nothing

here we avoid extra unpacking of data constructors. This is not that problematic however. For certain data structures like a ZipList the overhead will be more severe because the number of objects is larger.

On June 23, 2017, a new base library was published where the liftA2 function was added as a method to the Applicative type class .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM