“ T中的协变”和“ T中的反变”在Scala中到底意味着什么?

问题描述

我刚刚开始学习Scala,我正在上Odersky的课程,并且坚持理解方差。我也指的是这个博客-https://medium.com/@wiemzin/variances-in-scala-9c7d17af9dc4

我不理解对象扩展类型A的示例,但仍然会引发编译错误:

sealed trait A
case object B extends A
case object C extends A
case object D extends A

,然后再更新

val list: List[A] = Cons(B,Cons(C,Cons(D,Nil()))) //compile error: class List is invariant in type A.

我不明白为什么编译失败。对象BCD确实扩展了A,是A的子类型,所以为什么不允许此操作?


在我看来,“协变量”和“反变量”这两个词在某种程度上似乎并不直观,而且我无法理解博客文章中确切传达的内容。

这是强制执行严格的编译时类型检查的方法吗?我来自Java背景,但是以某种方式我发现这一切都很令人困惑。

解决方法

差异注释的目的是为了防止在编译时出现不希望得到的值。

考虑

sealed trait Animal
class Dog() extends Animal

存在三种有意义的方差类型。

  • 协方差:CovariantOf[Dog]CovariantOf[Animal]的子类型
  • 协方差:ContravariantOf[Animal]ContravariantOf[Dog]的子类型
  • 不变性:InvariantOf[Animal]InvariantOf[Dog]之间没有关系(即既不协变也不相反)

让我们首先考虑一些可以使我们受益的东西:

trait Producer[A] {
  def getOne: A
}

让我们想象Producer是逆变的:Producer[Animal]Producer[Dog]。那么我们可以:

class Wildebeest() extends Animal
val animalProducer = new Producer[Animal] { def getOne: Animal = new Wildebeest() }
val dogProducer: Producer[Dog] = animalProducer
val dog: Dog = dogProducer.getOne

animalProducer仅承诺产生Animal,而实际上仅产生Wildebeest,但是最后一行期望Dog。唯一棘手的事情是将Producer[Animal]视为Producer[Dog],因为一般的Producer[Animal]不会承诺只产生Dog。由于逆变意味着我们可以做到这一点,因此宣布Producer为逆变是不合法的。

另一方面,如果生产者是协变的,我们可以

val dogProducer = new Producer[Dog] { def getOne: Dog = new Dog() }
val animalProducer: Producer[Animal] = dogProducer
val animal: Animal = animalProducer.getOne

这显然是可以的,因为DogAnimal,所以可以声明

trait Producer[+A] {
  def getOne: A
}

同时,让我们建模一些我们只能产生Animal到的东西:Animal进来但它们不出来的东西:

trait Consumer[A] {
  def consume(a: A): Unit
}

Consumer应该是协变的吗?不,因为想象我们有一个狗叫声:

val dogPetter = new Consumer[Dog] {
  def consume(dog: Dog): Unit = println("petting a dog")
}

dogPetter只懂得如何养狗。让它pet成一团是很愚蠢的。但是,如果我们使Consumer为协变量,则可以将其设为pet slug:

 class Slug() extends Animal
 val animalPetter: Consumer[Animal] = dogPetter  // no cast needed,since covariance would mean a Consumer[Dog] is a Consumer[Animal]
 val slug = new Slug()
 animalPetter.consume(slug)  // Uh-oh,the dogPetter is petting a slug!

dogPetter的定义非常好。将Slug当作动物是完全可以的:Consumer[Animal]绝对应该能够consumeSlug。因此,问题必须出在将dogPetter视为Animal字符的情况下,这意味着问题在于协方差。

在另一面,假设我们有一个物体可以称赞Animal,任何Animal

val animalPraiser = new Consumer[Animal] {
  def consume(a: Animal): Unit = println("I praise this ${a.getClass} (like I should)")
 }

它可以明显地称赞DogSlug或任何其他Animal,所以这应该是有效的:

val dogPraiser: Consumer[Dog] = animalPraiser
dogPraiser.consume(new Dog)

因此,Consumer可以是反变量:

class Consumer[-A] { ... }

直觉(绝对正确)是,A中通用的类型如果不消耗A,则只能在A中进行协变;同样,如果它不产生A,则只能在A中变。

如果我们有一些可以生产和消费的东西怎么办?好吧,它必须是不变的,因为它不能协变,也不能不变。不变性是保守的默认值,因为假设协变或逆变会导致某些不合理的位置。

Java同样具有协方差和相反方注解:

  • Foo<? extends Bar>进行协方差
  • Foo<? super Bar>求逆

Scala的主要区别在于,在Java中,您将在使用站点上具有批注,而在Scala中,它将在定义站点上具有批注。 Scala方法可以减少重复的方差注释。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...