关于Scala中协方差的一些问题

Some questions about covariance in Scala

我试图理解 Scala 中的协方差,但我找不到任何示例来帮助我解决这个问题。 我有这个代码:

    class GenericCellImm[+T] (val x: T) {}

它编译得很好,但是当我使用它时

    class GenericCellMut[+T] (var x: T) { }

它无法编译。为什么我在写这段代码时不能使用var(但我可以使用val)?我该如何解决? 这里也是类似的情况

    abstract class Sequence[+A] {
    def append(x: Sequence[A]): Sequence[A]} 

有什么问题?

你不能写 def append(x: Sequence[A]): Sequence[A]},因为 A 在 append 参数中处于逆变位置,同时是协变的。

你应该这样写:

abstract class Sequence[+A] {
   def append[B >: A](x: Sequence[B]): Sequence[B]
}

您在 Why doesn't the example compile, aka how does (co-, contra-, and in-) variance work?

中有很好的解释

简而言之:

+A 声明在所有上下文中将此 A 转换为 A 的超类型是安全的(A Dog 可能转换为 Animal)。如果你写 append[A](x: Sequence[A]) 你是在声明 x 只能是 A 或 A 的子类型(Dog、yorsay 等),但绝不是超类型(如 Animal),因此这与 +A 注释相矛盾并且在编译时失败。使用签名 append[B >: A](x: Sequence[B]),您可以通过避免在函数参数中命名 A 来修复它。

因此,[B >: A] 定义了 B 的下限,声明 B 必须是 A 或 A 的超类型,但绝不是层次结构中低于 A 的任何 class,因此符合带有 +A 签名。

我知道协变和逆变是复杂的概念,很难理解,我也时不时感到困惑。

苹果是一种水果。

一袋不变的苹果就是一袋不变的水果。一袋苹果里装着苹果,是水果;你可以从这样的袋子里拿出一个苹果(好吧,不是真的,因为它是不可变的,但你可以取回苹果的 副本 ),最后得到一个水果在你手中。没问题。

但是可变的苹果袋不是可变的水果袋,因为您可以东西放入可变的水果袋中.例如,像香蕉。一袋苹果只能装苹果,不能装香蕉!

这正是 Scala 不允许第一个构造的原因。

第二个呢? Scala 允许您通过一些修改来执行此操作。但结果是逆变的。实际上,您可以创建 X[Fruit],这是一种 X[Apple]X有点像包包,只是作用方向相反。如果我们坚持水果类比,X 会是什么?把它想象成一个榨汁机。您可以将苹果放入苹果榨汁机中。您可以将任何种类的水果放入榨汁机中。有点自相矛盾的是,榨汁机是一种苹果榨汁机!苹果榨汁机唯一能做的就是榨苹果。但是榨汁机也可以做到这一点,这是一种(子类型)关系的基础。