协方差/逆变以及与消费者/生产者的关系

all*_*tar 0 covariance

我读过有关协方差/逆变的这篇文章:http://julien.richard-foy.fr/blog/2013/02/21/be-friend-with-covariance-and-convvariance/

这些例子非常清楚.但是,我正在努力理解最后得出的结论:

如果查看Run [+ A]和Vet [-A]的定义,您可能会注意到类型A仅出现在Run [+ A]方法的返回类型中,并且仅出现在Vet方法的参数中[-A] ].更一般地,产生类型A的值的类型可以在A上变成协变(就像在Run [+ A]中那样),并且消耗类型A的值的类型可以在A上变为逆变(就像你对Vet所做的那样) -一个]).

从上面的段落中你可以推断出只有getter的类型可以是协变的(换句话说,不可变数据类型可以是协变的,Scala标准库的大多数数据类型就是这种情况),但可变数据类型必然不变(他们有吸气剂和设定者,所以他们都生产和消费价值).

生产者:如果某些东西产生类型A,我可以想象一个类型A的引用变量被设置为类型A的对象或A的任何子类型,但不是超类型,因此它可以是协变的.

消费者:如果某些东西消耗了A类,我猜这意味着类型A可以用作方法中的参数.我不清楚这与协方差或逆变有什么关系.

从示例中可以看出,将类型指定为协变/逆变会影响其他函数如何使用它,但不确定它如何影响类本身.

Jul*_*Foy 6

从示例中可以看出,将类型指定为协变/逆变会影响其他函数如何使用它,但不确定它如何影响类本身.

这篇文章的重点是对一个班级用户的差异后果,而不是一个班级的实施者.

文章表明,协变和逆变类型为用户提供了更多的自由(因为一个接受Run[Mammal]有效接受a Run[Giraffe]或a 的函数Run[Zebra]).对于实现者来说,透视是双重的:协变和逆变类型为它们提供了更多的约束.

这些约束条件是协变类型不能出现在逆变位置,反之亦然.

例如考虑这种Producer类型定义:

trait Producer[+A] {
  def produce(): A
}
Run Code Online (Sandbox Code Playgroud)

类型参数A是协变的.因此我们只能在协变位置(例如方法返回类型)中使用它,但我们不能在逆变位置(例如方法参数)中使用它:

trait Producer[+A] {
  def produce(): A
  def consume(a: A): Unit // (does not compile because A is in contravariant position)
}
Run Code Online (Sandbox Code Playgroud)

为什么这样做是违法的?如果编译此代码会出现什么问题?那么,请考虑以下情况.首先,得到一些Producer[Zebra]:

val zebraProducer: Producer[Zebra] = …
Run Code Online (Sandbox Code Playgroud)

然后将它上传到一个Producer[Mammal](这是合法的,因为我们声称类型参数是协变的):

val mammalProducer: Producer[Mammal] = zebraProducer
Run Code Online (Sandbox Code Playgroud)

最后,用a来提供它Giraffe(这也是合法的,因为consume方法a Producer[Mammal]接受a Mammal,a Giraffe是a Mammal):

mammalProducer.consume(new Giraffe)
Run Code Online (Sandbox Code Playgroud)

但是,如果你记得很清楚,那mammalProducer实际上是一个zebraProducer,所以它的consume实现实际上只接受一个Zebra,而不是一个Giraffe!因此,在实践中,如果允许在逆变位置使用协变类型(就像我对consume方法所做的那样),那么类型系统将是不健全的.如果我们假设具有逆变类型参数的类也可以具有处于协变位置的方法(参见代码的末尾),我们可以构造类似的场景(导致荒谬).

(请注意,几种编程语言,例如Java或TypeScript,都有这种不健全的类型系统.)

实际上,在Scala中如果我们想在逆变位置使用协变类型参数,我们必须使用以下技巧:

trait Producer[+A] {
  def produce(): A
  def consume[B >: A](b: B): Unit
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,a Producer[Zebra]不会期望Zebraconsume方法中获得实际传递(但是任何类型的值B,更低限度Zebra),因此传递a是合法的Giraffe,这是a Mammal,这是一种超类型Zebra.


附录:类似的逆变情景

考虑以下类Consumer[-A],它具有逆变类型参数A:

trait Consumer[-A] {
  def consume(a: A): Unit
}
Run Code Online (Sandbox Code Playgroud)

假设类型系统允许我们定义一个A处于协变位置的方法:

trait Consumer[-A] {
  def consume(a: A): Unit
  def produce(): A // (does not actually compile because A is in covariant position)
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以得到一个实例Consumer[Mammal],将其向上转换Consumer[Zebra](因为逆转)并调用produce方法得到一个Zebra:

val mammalConsumer: Consumer[Mammal] = …
val zebraConsumer: Consumer[Zebra] = mammalConsumer // legal, because we claimed `A` to be contravariant
val zebra: Zebra = zebraConsumer.produce()
Run Code Online (Sandbox Code Playgroud)

但是,zebraConsumer实际上,我们mammalConsumer的方法produce可以返回任何方法Mammal,而不仅仅是Zebras.所以,最后,zebra可能会被初始化为一些Mammal不是Zebra!为了避免这种荒谬,类型系统禁止我们produceConsumer类中定义方法.