[…] 以下循环定义适用于类型liftA2
类。<*>
Applicative
(<*>) = liftA2 id
liftA2 f x y = f <$> x <*> y
liftA2
是可用于站点或站点上的非循环定义<*>
。
确切地说,这些不是方法的定义;它们是默认定义。具有一个参数的类型类只是一组类型,而instance
定义是该集合成员资格的入场费。Minimal
pragma forApplicative
告诉您必须实现这两种方法之一,并且该信息显示在 Haddock 文档中。
liftA2
、<*>
和的实际定义pure
特定于 的实例Applicative
。一般来说,如果一个类型类包含的方法只能使用该类型类的其他方法来实现,那么该方法并不一定是该类的一部分,因为它可以是一个带有约束的顶级定义。
然而,无论如何都可以包括这样的方法。这可能只是为了方便,因为它更容易根据一个函数定义一个实例,即使它不是“最基本的”操作。这通常也是出于性能原因:当可以比特定类型的默认方法更有效地实现方法时,往往会包含冗余方法。例如,在这种情况下,liftA2
可能能够更有效地一起<$>
遍历两个结构,而不是使用 遍历一个结构,然后使用 单独遍历另一个结构<*>
。
GHC还提供DefaultSignatures
了一种添加更具体的默认值的方法,通常根据.Generic
deriving
这种完全循环的引用是一种疏忽吗?
一点也不,他们是故意的。类型类方法的默认实现中的循环定义非常普遍。例如,Eq
在 Haskell 报告中定义如下:
class Eq a where
(==) :: a -> a -> Bool
x == y = not (x /= y)
(/=) :: a -> a -> Bool
x /= y = not (x == y)
可能会忘记实现其中之一,因此它们都使用默认值,因此代表一个无限循环,但是:
因此,在这种情况下,实际上唯一的其他选择是从类中删除一个或另一个,或者省略为其中一个提供默认值。如果你想知道这些方法是如何实现的Applicative
,要看的是instance
具体类型构造函数的实现,比如[]
, ZipList
, Maybe
,StateT
等等。