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

时间:2017-11-10 16:21:21

标签: covariance

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

这些例子很清楚。但是,我很难理解最后得出的结论:

  

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

     

从上一段你可以推断出那些只有的类型   getter可以是协变的(换句话说,不可变的数据类型可以是   协变,Scala的大多数数据类型就是这种情况   标准库),但可变数据类型必然是不变的   (他们有吸气剂和二传手,所以他们都生产和消费   值)。

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

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

从示例中可以看出,将一个类型指定为协变/逆变会影响它如何被其他函数消耗,但不确定它如何影响类本身。

1 个答案:

答案 0 :(得分:6)

  

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

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

文章显示协变和逆变类型为用户提供了更多自由(因为接受Run[Mammal]的函数有效地接受了Run[Giraffe]Run[Zebra] )。对于实现者,透视是双重的:协变和逆变类型为它们提供了更多约束

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

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

trait Producer[+A] {
  def produce(): A
}

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

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

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

val zebraProducer: Producer[Zebra] = …

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

val mammalProducer: Producer[Mammal] = zebraProducer

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

mammalProducer.consume(new Giraffe)

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

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

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

trait Producer[+A] {
  def produce(): A
  def consume[B >: A](b: B): Unit
}

在这种情况下,Producer[Zebra]不希望在Zebra方法中传递实际的consume(但是类型为B的任何值都是有限的Zebra),因此传递GiraffeMammal是合法的,ZebraConsumer[-A]的超类型。

附录:类似的逆变情景

考虑以下类A,它具有逆变型参数trait Consumer[-A] { def consume(a: A): Unit }

A

假设类型系统允许我们定义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方法获取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。因此,最后,Mammal可能会被初始化为某个Zebra,而不是produce!为了避免这种荒谬,类型系统禁止我们在Consumer类中定义app/Exception/Handler.php方法。