我读过这篇关于协方差/反方差的文章: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进行了反变异(就像你对兽医[-A]所做的那样)。
从上面的段落中,你可以推断出只有getter可以是协变的(换句话说,不可变的数据类型可以是协变,Scala的大多数数据类型都是这样标准库),但是可变的数据类型必然是不变的(他们有getter和setter,所以他们既生产又消费值)。
Producers:如果某个东西产生类型A,我可以想象一些类型A的引用变量被设置为类型A的对象或A的任何子类型,但不是超类型,所以它可以是协变的是合适的。
消费者:如果某个东西使用类型A,我想这意味着类型A可以用作方法中的参数。我不清楚这和协方差或反方差有什么关系。
从示例中可以看出,将类型指定为协变/反变会影响其他函数使用它的方式,但不确定它会如何影响类本身。
从示例中可以看出,将类型指定为协变/逆变变量会影响其他函数使用它的方式,但不确定它会如何影响类本身。
这篇文章关注的是差异对类的用户的影响,而不是对类的实现者的影响,这是正确的。
文章表明,协变和逆变类型为用户提供了更多的自由度(因为接受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
s。因此,最后,zebra
可能会初始化为某个不是Zebra
的Mammal
!为了避免这种荒谬,类型系统禁止我们在Consumer
类中定义produce
方法。