协方差对我来说非常直观,但我对逆方差的理解有点不稳定。 我理解Function[-A, +B]
中的逆方差,因为Animal => Int
函数可以在使用Rabbit => Int
函数的任何地方使用。 我真的不明白这种逆变关系如何适用于Builder
的Elem
项,特别是为什么Builder
的Elem
是逆变的,而TraversableLike
的Elem
是协变的。
有一个经验法则:
逆方差用于函数参数,方差用于函数返回类型
我将对此进行详细说明。通常,如果您有以下函数:
def f(a: A): B
您可以将 A 的所有子类型的任何实例传递给此函数,并将结果分配给任何超类型 B 类型的值
因此,假设您具有以下泛型类型:
特质生成器[Elem, To]
这意味着您可以添加Elem
类型的元素以形成 To
类型的实例。例如,具有以下类型:
class Animal
class Cat extends Animal
class Dog extends Animal
class Tiger extends Cat
val a: Builder[Cat, ArrayBuffer[Cat]]
为您提供一个构建器来构建包含 Cat
类型元素的ArrayBuffer
。我们想要的是一个更通用的结构。让我们看一下以下示例:
implicit val builder: Builder[Cat, ArrayBuffer[Cat]] = ...
def build(elements: Tiger*)(implicit b: Builder[Tiger, ArrayBuffer[Animal]]): ArrayBuffer[Animal] = { ... }
我们有一个构建函数,它需要一个隐式的范围,它可以从Tiger
实例数组中ArrayBuffer[Animal]
。让我们回到一些逻辑。
- 将
ArrayBuffer[Cat]
分配给类型为ArrayBuffer[Animal]
的值是合乎逻辑的。- 在
ArrayBuffer[Cat]
中添加Tiger
是合乎逻辑的,因为老虎是一种猫。
因此,我们的build
函数在逻辑上应该能够使用隐式 val builder
来形成 ArrayBuffer[Cat],或者更一般地说,从给定的Tiger
实例集合中形成 ArrayBufer[Animal]。这只有在保持以下关系时才有可能:
Builder[Cat, ArrayBuffer[Cat]] <: Builder[Tiger, ArrayBuffer[Animal]]
在这种情况下,可以在调用函数时选择builder
b
build
参数。
实现此关系的唯一方法是将生成器特征更改为以下内容:
trait Builder[-Elem, +To]
所以自Tiger <: Cat
和Cat <: Animal
以来,根据Elem
的逆方差和To
的方差,Builder[Cat, ArrayBuffer[Cat]] <: Builder[Tiger, ArrayBuffer[Animal]]
。
回到介绍性的经验法则:您希望使用类型 Elem
的元素来形成类型 To
的集合。 将其想象成一个函数,其中Elem
是参数类型,To
是返回类型。因此,参数类型的逆方差和返回类型的方差使其成为逻辑上正确的泛型函数。
让我们从构建器开始,正如文档所说:
所有建设者的基本特征。构建器允许增量构造集合,方法是使用 += 向构建器添加元素,然后转换为具有结果的所需集合类型。
假设我们有一个将 Builder 作为参数的函数。
def addCatAndBuild(builder : Builder[Cat, Buffer]) = {
builder += new Cat()
builder.result()
}
我们在这里唯一可以使用的构建器是那些将 Cat 的超类型指定为其 Elem 参数的构建器(您可以将 Cat 添加到任何动物集合中)。Builder[Animal, Buffer] 可以在这里使用,但不能使用 Builder[ThreeLeggedCat, Buffer](因为不是每只猫只有三条腿)。
至于 TraversableLike - 这个特征没有 Elem (或 A,如在 scaladoc) 实例参数中的方法。有一些方法接受 Elem => 任何函数。一些方法返回 elem 的其他集合,(例如。List[Elem]),所有这些集合都有协变类型参数。
就 TraversableLike 的操作而言,你在 TraversableLike[Animal, Repr] 上所做的任何事情也可以在 TraversableLike[Cat, Repr] 上完成。
def allCanWalk(tl : TraversableLike[Animal, Repr]) =
tl.forall(_.canWalk)
总而言之,它是类的接口,它是类型参数的使用,允许它们是协变的或逆变的。