首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >协方差/反方差及其与消费者/生产者的关系

协方差/反方差及其与消费者/生产者的关系
EN

Stack Overflow用户
提问于 2017-11-10 16:21:21
回答 1查看 384关注 0票数 0

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

例子非常清楚。然而,我很难理解最后得出的结论:

如果您查看Run+A和Vet的定义,您可能会注意到,类型A只出现在Run+A方法的返回类型中,并且只出现在Vet方法的参数中。更普遍的情况是,生成A类型值的类型可以在A上进行协变(就像您对Run+A所做的那样),而在A上使用A类型值的类型可以在A上生成反变体(就像您对Vet所做的那样)。 从上面的段落中,您可以推断只有getter的类型可以是协变量的(换句话说,不可变的数据类型可以是协变的,Scala标准库的大多数数据类型也是这样),但是可变的数据类型必然是不变的(它们有getters和setter,因此它们都生成和使用值)。

生产者:如果某物产生A类型,我可以想象A类型的引用变量被设置为A类型的对象或A的任何子类型,而不是超级类型,所以它可以是协变的。

消费者:如果某物消耗了A型,我猜这意味着A型可能被用作方法中的参数。我不清楚这与协方差或反方差有什么关系。

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

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-11-13 11:18:50

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

这篇文章关注的是类的用户,而不是类的实现者,这是正确的。

本文表明,协变和反变类型给用户提供了更多的自由(因为接受Run[Mammal]的函数实际上接受Run[Giraffe]Run[Zebra])。对于实现者来说,视角是双重的:协变和反变类型给了他们更多的约束。

这些约束条件是,协变类型不能发生在对变量位置,反之亦然。

例如,考虑一下这个Producer类型定义:

代码语言:javascript
复制
trait Producer[+A] {
  def produce(): A
}

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

代码语言:javascript
复制
trait Producer[+A] {
  def produce(): A
  def consume(a: A): Unit // (does not compile because A is in contravariant position)
}

为何这样做是违法的呢?如果编译了这段代码,会有什么问题呢?那么,考虑一下下面的场景。首先,得到一些Producer[Zebra]

代码语言:javascript
复制
val zebraProducer: Producer[Zebra] = …

然后将其向上转换为Producer[Mammal] (这是合法的,因为我们将类型参数声明为协变量):

代码语言:javascript
复制
val mammalProducer: Producer[Mammal] = zebraProducer

最后,使用Giraffe (这也是合法的,因为consume方法Producer[Mammal]接受MammalGiraffeMammal):

代码语言:javascript
复制
mammalProducer.consume(new Giraffe)

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

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

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

代码语言:javascript
复制
trait Producer[+A] {
  def produce(): A
  def consume[B >: A](b: B): Unit
}

在这种情况下,Producer[Zebra]不会期望获得在consume方法中传递的实际Zebra (但是类型B的任何值,其下界为Zebra),因此传递Giraffe是合法的,这是一个Mammal,它是一种超级Zebra类型。

附录:反差额的类似情况

考虑下面的类Consumer[-A],它有一个反变型参数A

代码语言:javascript
复制
trait Consumer[-A] {
  def consume(a: A): Unit
}

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

代码语言:javascript
复制
trait Consumer[-A] {
  def consume(a: A): Unit
  def produce(): A // (does not actually compile because A is in covariant position)
}

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

代码语言:javascript
复制
val mammalConsumer: Consumer[Mammal] = …
val zebraConsumer: Consumer[Zebra] = mammalConsumer // legal, because we claimed `A` to be contravariant
val zebra: Zebra = zebraConsumer.produce()

然而,我们的zebraConsumer实际上是mammalConsumer,它的方法produce可以返回任何Mammal,而不仅仅是Zebra的。因此,最终,zebra可能被初始化为一些不是ZebraMammal!为了避免这种荒谬,类型系统禁止我们在produce类中定义Consumer方法。

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47227098

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档