我读过这篇关于协方差/反方差的文章: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型可能被用作方法中的参数。我不清楚这与协方差或反方差有什么关系。
从示例中可以看出,将类型指定为协变/反变似乎会影响其他函数如何使用它,但不确定它如何影响类本身。
发布于 2017-11-13 11:18:50
从示例中可以看出,将类型指定为协变/反变似乎会影响其他函数如何使用它,但不确定它如何影响类本身。
这篇文章关注的是类的用户,而不是类的实现者,这是正确的。
本文表明,协变和反变类型给用户提供了更多的自由(因为接受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,Giraffe是Mammal):
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]不会期望获得在consume方法中传递的实际Zebra (但是类型B的任何值,其下界为Zebra),因此传递Giraffe是合法的,这是一个Mammal,它是一种超级Zebra类型。
附录:反差额的类似情况
考虑下面的类Consumer[-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可能被初始化为一些不是Zebra的Mammal!为了避免这种荒谬,类型系统禁止我们在produce类中定义Consumer方法。
https://stackoverflow.com/questions/47227098
复制相似问题