我在Haskell邮件列表中遇到了这个discussion。从讨论中可以看出,添加liftA2作为Applicative方法可能会对性能产生影响。您能否提供具体示例,说明为什么有必要在应用方法中添加liftA2?
最佳答案
这封电子邮件写于2017年。那时 Applicative
typeclass看起来像:
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
因此,不将
liftA2
作为Applicative
类型类的一部分。这是defined as [src]:liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f a b = fmap f a <*> b
因此无法在typeclass中进行特殊实现。这意味着有时
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
因此,这意味着实现
liftA2
的Maybe
类似于: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
但这不是最佳的。这意味着
fmapMaybe
将检查参数是否为Just x
或Nothing
,然后返回Just (f x)
或Nothing
。但是不管怎样,apMaybe
会再次检查它,而我们已经事先知道了。我们可以通过以下方式实现更有效的实施:liftA2Maybe :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
liftA2Maybe f (Just x) (Just y) = Just (f x y)
liftA2Maybe _ _ _ = Nothing
在这里,我们避免了数据构造函数的额外拆包。但是,这并不是问题。对于某些数据结构(例如
ZipList
),开销将更为严重,因为对象数量更多。2017年6月23日,发布了一个新的
base
库,其中liftA2
函数作为方法添加到 Applicative
type class中。