为什么将liftA2作为方法添加到Applicative?

man*_*old 7 haskell functional-programming

我在 Haskell 邮件列表上遇到了这个讨论。从讨论来看,添加liftA2 作为Applicative 的方法似乎对性能有影响。你能提供具体的例子,为什么需要在 Applicative 方法中添加 liftA2 吗?

Wil*_*sem 10

邮件是 2017 年写的。 当时的Applicativetypeclass是这样的:

class Functor f => Applicative f where
    -- | Lift a value.
    pure :: a -> f a

    -- | Sequential application.
    (<*>) :: f (a -> b) -> f a -> f b

    -- | Sequence actions, discarding the value of the first argument.
    (*>) :: f a -> f b -> f b
    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.
    (<*) :: f a -> f b -> f a
    (<*) = liftA2 const
Run Code Online (Sandbox Code Playgroud)

所以没有liftA2作为Applicative类型类的一部分。它被定义为 [src]

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f a b = fmap f a <*> b
Run Code Online (Sandbox Code Playgroud)

因此无法在类型类中进行特殊实现。这意味着有时liftA2可以更有效地实施,但无法定义。

例如Maybe函子和Applicative被实现为:

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
Run Code Online (Sandbox Code Playgroud)

因此,这意味着liftA2for aMaybe的实现类似于:

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
Run Code Online (Sandbox Code Playgroud)

但这不是最优的。这意味着fmapMaybe将检查参数是否为 aJust xNothing,然后返回 aJust (f x)或 a Nothing。但无论如何,apMaybe再次检查,而我们已经可以提前知道了。我们可以通过以下方式进行更有效的实施:

liftA2Maybe :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
liftA2Maybe f (Just x) (Just y) = Just (f x y)
liftA2Maybe _ _ _ = Nothing
Run Code Online (Sandbox Code Playgroud)

在这里,我们避免了对数据构造函数的额外解包。然而,这并不是那么有问题。对于像 a 这样的某些数据结构ZipList,开销会更严重,因为对象的数量更大。

2017 年 6 月 23 日,发布了一个新base库,其中将liftA2函数作为方法添加到Applicative类型 class 中