generics - 为什么示例不能编译,也就是(co-、contra- 和 in-)方差如何工作?

标签 generics scala covariance contravariance

this question ,有人可以在 Scala 中解释以下内容:

class Slot[+T] (var some: T) { 
   //  DOES NOT COMPILE 
   //  "COVARIANT parameter in CONTRAVARIANT position"

}

我理解 之间的区别+T T 在类型声明中(如果我使用 T 则编译)。但是,如何在不诉诸于创建未参数化的事物的情况下实际编写一个在其类型参数中具有协变的类呢?如何确保以下内容只能使用 的实例创建T ?
class Slot[+T] (var some: Object){    
  def get() = { some.asInstanceOf[T] }
}

编辑 - 现在将其归结为以下内容:
abstract class _Slot[+T, V <: T] (var some: V) {
    def getT() = { some }
}

这一切都很好,但我现在有两个类型参数,我只想要一个。我会重新问这个问题:

我如何写一个不可变的 Slot类型协变的类?

编辑 2 : 呸!我用过 var而不是 val .以下是我想要的:
class Slot[+T] (val some: T) { 
}

最佳答案

通常,协变类型参数是一种允许随着类的子类型化而向下变化的参数(或者,随着子类型化而变化,因此是“co-”前缀)。更具体地说:

trait List[+A]
List[Int]List[AnyVal] 的子类型因为 IntAnyVal 的子类型.这意味着您可以提供 List[Int] 的实例。当类型为 List[AnyVal] 的值时预计。这确实是泛型工作的一种非常直观的方式,但事实证明,在存在可变数据的情况下使用时,它是不健全的(破坏了类型系统)。这就是泛型在 Java 中不变的原因。使用 Java 数组(错误地协变)的不健全的简要示例:
Object[] arr = new Integer[1];
arr[0] = "Hello, there!";

我们刚刚分配了一个类型为 String 的值到 Integer[] 类型的数组.出于显而易见的原因,这是个坏消息。 Java 的类型系统实际上在编译时允许这样做。 JVM 将“帮助”抛出 ArrayStoreException在运行时。 Scala 的类型系统防止了这个问题,因为 Array 上的类型参数类是不变的(声明是 [A] 而不是 [+A] )。

请注意,还有另一种称为逆变的方差。这非常重要,因为它解释了为什么协方差会导致一些问题。逆变实际上与协方差相反:参数随子类型而向上变化。它不太常见,部分原因是它违反直觉,尽管它确实有一个非常重要的应用:函数。
trait Function1[-P, +R] {
  def apply(p: P): R
}

请注意 P 上的“ - ”方差注释类型参数。该声明作为一个整体意味着 Function1P 中是逆变的和协变 R .因此,我们可以推导出以下公理:
T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']

请注意 T1'必须是 T1 的子类型(或相同类型) ,而 T2 则相反和 T2' .在英语中,这可以理解为:

A function A is a subtype of another function B if the parameter type of A is a supertype of the parameter type of B while the return type of A is a subtype of the return type of B.



这条规则的原因留给读者作为练习(提示:考虑不同的情况,因为函数是子类型的,就像我上面的数组示例一样)。

有了新发现的协变和逆变知识,您应该能够理解为什么以下示例无法编译:
trait List[+A] {
  def cons(hd: A): List[A]
}

问题是A是协变的,而 cons函数期望它的类型参数是不变的。因此,A正在改变错误的方向。有趣的是,我们可以通过制作 List 来解决这个问题。 A 中的逆变,然后返回类型 List[A]将无效,因为 cons函数期望它的返回类型是协变的。

我们这里唯一的两个选择是 a) make A不变,失去协方差的漂亮、直观的子类型属性,或者 b) 向 cons 添加一个局部类型参数定义 A 的方法作为下限:
def cons[B >: A](v: B): List[B]

这是现在有效的。你可以想象 A向下变化,但 B能够相对于 A 向上变化自 A是它的下限。通过这个方法声明,我们可以得到 A是协变的,一切都会好起来的。

请注意,此技巧仅在我们返回 List 的实例时才有效。它专门用于不太具体的类型 B .如果您尝试制作 List可变的,由于您最终尝试分配类型 B 的值,因此事情会崩溃。到 A 类型的变量,这是编译器不允许的。每当您有可变性时,您就需要有某种类型的增变器,它需要某种类型的方法参数,这(与访问器一起)意味着不变性。协方差适用于不可变数据,因为唯一可能的操作是访问器,它可以被赋予协变返回类型。

关于generics - 为什么示例不能编译,也就是(co-、contra- 和 in-)方差如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/663254/

相关文章:

linux - 如何在 Spark 中编写一个独立应用程序,以在充满提取推文的文本文件中查找 20 个最常提及的内容

java - Map key/value类型限制,如何关联?

c# - C#协方差混淆

java - 泛型和静态方法

java - 是否可以评估一个类接口(interface)是否扩展了某个基类?

c# - 通用检查 Entity Framework 和存储库模式是否发生了变化

c# - 在 C# 中访问非泛型类型

Scala FlatMap 提供错误的结果

c# - 使协变接口(interface)向后兼容

c#引用理解?