该示例为何不编译,又是(协方,对方和内部)方差如何工作?


147

接着这个问题,有人可以在Scala中解释以下内容:

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

}

我明白之间的区别+T,并T在类型声明(它编译如果我使用T)。但随后一个人如何真正写一个类,这是在其类型参数的协变而不诉诸创造的东西unparametrized?如何确保只能使用实例创建以下内容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:Du!我用过var,没有用过val。以下是我想要的:

class Slot[+T] (val some: T) { 
}

6
因为var可设定而val不是。这也是为什么scala的不可变集合是协变的,而可变的集合却不是。
oxbow_lakes

在这种情况下,这可能很有趣:scala-lang.org/old/node/129
user573215 2013年

Answers:


302

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

trait List[+A]

List[Int]是的子类型,List[AnyVal]因为Int是的子类型AnyVal。这意味着您可以提供List[Int]何时需要类型值的实例List[AnyVal]。这实际上是泛型工作的非常直观的方式,但事实证明,在存在可变数据的情况下使用泛型是不合理的(破坏类型系统)。这就是为什么泛型在Java中是不变的。使用Java数组(错误协变量)的不健全的简要示例:

Object[] arr = new Integer[1];
arr[0] = "Hello, there!";

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

请注意,还有另一种类型的方差,称为convariance。这非常重要,因为它说明了为什么协方差会导致某些问题。字面上的相反是协方差:参数随着子类型的变化而向上变化。尽管它确实有一个非常重要的应用程序:函数,但它却不那么直观,部分原因是它是如此违反直觉。

trait Function1[-P, +R] {
  def apply(p: P): R
}

注意类型参数上的“ - ”方差注释P。总体Function1上讲,该声明在中是P协变的R。因此,我们可以得出以下公理:

T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']

请注意,它T1'必须是的子类型(或相同类型)T1,而与T2和相反T2'。用英语可以理解为以下内容:

如果A的参数类型是B的参数类型的超类型,而A的返回类型是B的返回类型的子类型,则函数A是另一个函数B的子类型。

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

有了新发现的协方差和逆方差知识,您应该能够理解为什么以下示例无法编译:

trait List[+A] {
  def cons(hd: A): List[A]
}

问题是A协变,而cons函数希望其类型参数不变。因此,A正在改变错误的方向。有趣的是,我们可以通过使Listin 成为变量来解决此问题A,但是List[A]由于cons函数期望其返回类型为covariant,因此返回类型将无效。

我们这里仅有的两个选择是:a)使A不变,失去协方差的漂亮,直观的子类型属性,或b)向cons定义A为下限的方法中添加局部类型参数:

def cons[B >: A](v: B): List[B]

现在有效。您可以想象它A是向下变化的,但是由于它的下限,B因此能够相对向上变化。使用此方法声明,我们可以是协变的,一切都可以进行。AAA

注意,只有当我们返回List专门针对次特定类型的实例时,此技巧才有效B。如果尝试使变量List可变,则由于最终尝试将type的值分配给type B的变量而A导致编译失败,编译器不允许这样做。只要具有可变性,就需要具有某种可变器,该可变器需要某种类型的方法参数(与访问器一起使用)意味着不变性。协方差适用于不可变数据,因为唯一可能的操作是访问器,可以为访问器指定协变返回类型。


4
可以用简单的英语表示为-您可以将更简单的内容作为参数,而可以返回更复杂的内容吗?
菲尔(Phil)

1
Java编译器(1.7.0)无法编译“ Object [] arr = new int [1];” 而是给出错误消息:“ java:所需的不兼容类型:java.lang.Object []找到:int []”。我认为您的意思是“ Object [] arr = new Integer [1];”。
EmreSevinç13年

2
当您提到“将这个规则的原因留给读者练习时(提示:当函数被子类型化时,请考虑不同的情况,就像我上面的数组示例一样)”。您能否实际举几个例子?
perryzheng 2014年

2
每@perryzheng ,取trait Animaltrait Cow extends Animaldef iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)def iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a)。这样iNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})就可以了,因为我们的动物群可以放牧牛,但是 iNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})会产生编译错误,因为我们的牛群不能放牧所有动物。
Lasf 2015年

这是相关的,并为我带来了变化:typelevel.org/blog/2016/02/04/variance-and-functors.html
彼得·施密茨

27

@Daniel已经很好地解释了。但简而言之,如果允许的话:

  class Slot[+T](var some: T) {
    def get: T = some   
  }

  val slot: Slot[Dog] = new Slot[Dog](new Dog)   
  val slot2: Slot[Animal] = slot  //because of co-variance 
  slot2.some = new Animal   //legal as some is a var
  slot.get ??

slot.get然后会在运行时引发错误,因为它无法将转换AnimalDog(duh!)。

通常,可变性不能与协方差和逆方差很好地配合。这就是所有Java集合都是不变的原因。


7

有关此内容的完整讨论,请参见示例Scala,第57+页。

如果我正确理解了您的评论,则需要从第56页的底部开始重新阅读这段内容(基本上,我认为您要求的是没有运行时检查就不是类型安全的,而scala则不行,因此您很不走运)。翻译他们的示例以使用您的构造:

val x = new Slot[String]("test") // Make a slot
val y: Slot[Any] = x             // Ok, 'cause String is a subtype of Any
y.set(new Rational(1, 2))        // Works, but now x.get() will blow up 

如果您觉得我不明白您的问题(很可能),请尝试在问题描述中添加更多的解释/上下文,我将再次尝试。

根据您的编辑:不可变插槽是完全不同的情况... *微笑*希望以上示例对您有所帮助。


我读过;不幸的是,我(仍然)不明白我该怎么做(上面我实际上在T中写了一个参数化的类协变量)
oxbow_lakes 2009年

我取消了这个标记,因为我意识到这有点苛刻。我应该在问题中明确指出,我已通过示例阅读了Scala的内容;我只是想以一种“非正式的”方式来解释它
oxbow_lakes 2009年

@oxbow_lakes 微笑我担心Scala通过示例不太正式的解释。充其量,我们可以尝试在此处使用具体示例进行工作……
MarkusQ,2009年

抱歉-我不希望我的位置可变。我刚刚意识到问题是我声明了var而不是val
oxbow_lakes

3

您需要对参数应用下限。我很难记住该语法,但是我认为它看起来像这样:

class Slot[+T, V <: T](var some: V) {
  //blah
}

以示例为例的Scala有点难以理解,一些具体示例会有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.