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