Scala 集合:为什么 Elem 在 Builder 中是逆变的,而在 TraversableLike 中是协变的?

Scala collections: Why is Elem contravariant in Builder but covariant in TraversableLike?

协方差对我来说很直观,但我对协方差的理解有点不稳定。我理解 Function[-A, +B] 中的反方差,因为 Animal => Int 函数可以在任何使用 Rabbit => Int 函数的地方使用。我真的不明白这种同类型的逆变关系如何适用于 BuilderElem 术语,特别是为什么 BuilderElem 是相反的-variant 但 TraversableLikeElem 是协变的。

让我们从构建器开始,正如文档所说:

The base trait of all builders. A builder lets one construct a collection incrementally, by adding elements to the builder with += and then converting to the required collection type with result.

假设我们有一个将 Builder 作为参数的函数。

def addCatAndBuild(builder : Builder[Cat, Buffer]) = {
    builder += new Cat()
    builder.result()
}

我们在这里唯一可以使用的构建器是那些将超类型 Cat 指定为其 Elem 参数的构建器(您可以将 Cat 添加到任何 Animal 集合中)。这里可以用Builder[Animal, Buffer],不能用Builder[ThreeLeggedCat, Buffer](因为不是每只猫都只有三条腿)

至于 TraversableLike - 此特征没有带有 Elem(或 A,如在 scaladoc 中)实例参数的方法。有接受 Elem => Whatever 功能的方法。一些方法 return Elem 的其他集合(例如 List[Elem]),并且所有这些集合都具有协变类型参数。

在 TraversableLike 的操作方面,你在 TraversableLike[Animal, Repr] 上所做的任何事情也可以在 TraversableLike[Cat, Repr] 上完成。

def allCanWalk(tl : TraversableLike[Animal, Repr]) =
   tl.forall(_.canWalk)

总而言之,就是class的接口和类型参数的使用,让它们可以协变或逆变。

有一个经验法则:

contra-variance is for function parameters and variance is for function return type

我将对此进行详细说明。一般来说,如果你有这样的功能:

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]]

为您提供构建 ArrayBuffer 的构建器,其中包含 Cat 类型的元素。我们想要的是一个更通用的结构。让我们看看下面的例子:

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 函数在逻辑上应该能够使用 builder 隐式 val 来形成 ArrayBuffer[Cat] 或更一般地从给定的 [=20= 集合中形成 ArrayBufer[Animal] ] 实例。这只有在满足以下关系时才有可能:

Builder[Cat, ArrayBuffer[Cat]] <: Builder[Tiger, ArrayBuffer[Animal]]

在这种情况下,builder 可以在调用 build 函数时被选为 b 参数。

实现这种关系的唯一方法是将 Builder 特征更改为以下内容:

trait Builder[-Elem, +To]

因此,由于 Tiger <: CatCat <: Animal,根据 Elem 上的反方差和 To 上的方差,Builder[Cat, ArrayBuffer[Cat]] <: Builder[Tiger, ArrayBuffer[Animal]].

回到介绍性的经验法则:您想使用 Elem 类型的元素来形成 To 类型的集合。把它想象成一个函数,其中 Elem 是参数类型,To 是 return 类型。因此,参数类型的反方差和 return 类型的方差使它成为逻辑上正确的泛型函数。