我在Haskell邮件列表上看到了这个讨论。从讨论中可以看出,添加liftA2作为应用程序的方法似乎会对性能产生影响。你能提供具体的例子说明为什么有必要在应用方法中添加liftA2吗?
这封电子邮件写于2017年。当时Applicative
类型类看起来像:
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
类型类的一部分。它被定义为[src]:
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c liftA2 f a b = fmap f a <*> b
因此无法在类型类中进行特殊实现。这意味着有时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
因此,这意味着Maybe
的liftA2
类似于来实现
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
类型类的方法。