Scala 集合:为什么 Elem 在 Builder 中是逆变的,而在 TraversableLike 中是协变的?
Scala collections: Why is Elem contravariant in Builder but covariant in TraversableLike?
协方差对我来说很直观,但我对协方差的理解有点不稳定。我理解 Function[-A, +B]
中的反方差,因为 Animal => Int
函数可以在任何使用 Rabbit => Int
函数的地方使用。我真的不明白这种同类型的逆变关系如何适用于 Builder
的 Elem
术语,特别是为什么 Builder
的 Elem
是相反的-variant 但 TraversableLike
的 Elem
是协变的。
让我们从构建器开始,正如文档所说:
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 <: Cat
和 Cat <: Animal
,根据 Elem
上的反方差和 To
上的方差,Builder[Cat, ArrayBuffer[Cat]] <: Builder[Tiger, ArrayBuffer[Animal]]
.
回到介绍性的经验法则:您想使用 Elem
类型的元素来形成 To
类型的集合。把它想象成一个函数,其中 Elem
是参数类型,To
是 return 类型。因此,参数类型的反方差和 return 类型的方差使它成为逻辑上正确的泛型函数。
协方差对我来说很直观,但我对协方差的理解有点不稳定。我理解 Function[-A, +B]
中的反方差,因为 Animal => Int
函数可以在任何使用 Rabbit => Int
函数的地方使用。我真的不明白这种同类型的逆变关系如何适用于 Builder
的 Elem
术语,特别是为什么 Builder
的 Elem
是相反的-variant 但 TraversableLike
的 Elem
是协变的。
让我们从构建器开始,正如文档所说:
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 <: Cat
和 Cat <: Animal
,根据 Elem
上的反方差和 To
上的方差,Builder[Cat, ArrayBuffer[Cat]] <: Builder[Tiger, ArrayBuffer[Animal]]
.
回到介绍性的经验法则:您想使用 Elem
类型的元素来形成 To
类型的集合。把它想象成一个函数,其中 Elem
是参数类型,To
是 return 类型。因此,参数类型的反方差和 return 类型的方差使它成为逻辑上正确的泛型函数。