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

时间:2015-03-16 08:57:45

标签: scala collections covariance contravariance

协方差对我来说非常直观,但我对矛盾方差的理解有点不稳定。我理解Function[-A, +B]中的反差异,因为Animal => Int函数可以在使用Rabbit => Int函数的任何地方使用。我真的不明白这种相反的变异关系如何适用于Builder的{​​{1}}字词,特别是为什么Elem' s Builder是反变体,但Elem TraversableLike是协变的。

2 个答案:

答案 0 :(得分:0)

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

  

所有建造者的基本特征。构建器允许通过使用+ =向构建器添加元素,然后使用结果转换为所需的集合类型,逐步构造集合。

假设我们有一个以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 =>的方法。无论什么功能。一些方法返回Elem的其他集合(例如,List [Elem]),并且所有这些集合都具有协变类型参数。

就TraversableLike的操作而言,您在TraversableLike [Animal,Repr]上所做的任何事情也可以在TraversableLike [Cat,Repr]上完成。

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

总结一下,它是类的接口,它的类型参数的使用允许它们是协变的或逆变的。

答案 1 :(得分:0)

有一条经验法则:

  

对比方差用于函数参数,方差用于函数返回类型

我将详细说明这一点。通常,如果你有这样的功能:

def f(a: A): B

您可以将所有子类型A 的任何实例传递给此函数,并将结果分配给任何超类型B <的类型值/ p>

因此,假设您有以下通用类型:

trait Builder [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] = { ... }

我们有一个构建函数,它需要在作用域中隐含一个ArrayBuffer[Animal]实例的Tiger。让我们回到一些逻辑。

  • ArrayBuffer[Cat]分配给类型为ArrayBuffer[Animal]的值是合乎逻辑的。
  • Tiger添加到ArrayBuffer[Cat]是合乎逻辑的,因为Tiger有点像猫。

因此,我们的build函数在逻辑上应该能够使用builder隐式val来形成ArrayBuffer [Cat]或更一般地从给定的Tiger个实例集合中形成ArrayBufer [Animal] 。只有在保持以下关系时才可以这样做:

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

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

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

trait Builder[-Elem, +To]

Tiger <: CatCat <: Animal以来,根据Elem的反差异和To上的差异,Builder[Cat, ArrayBuffer[Cat]] <: Builder[Tiger, ArrayBuffer[Animal]]

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