当前位置:  开发笔记 > 编程语言 > 正文

为什么示例不编译,又如何(共同,反向和反向)方差有效?

如何解决《为什么示例不编译,又如何(共同,反向和反向)方差有效?》经验,为你挑选了3个好方法。

继这个问题之后,有人可以在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) { 
}

Daniel Spiew.. 299

通常,协变类型参数是允许在类被子类型时向下变化的参数(或者,随着子类型而变化,因此是"共 - "前缀).更具体地说:

trait List[+A]

List[Int]List[AnyVal]因为Int是子类型的子类型AnyVal.这意味着您可以提供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
}

注意type参数上的" - "方差注释P.这个声明作为一个整体意味着它Function1是逆变的P和协变的R.因此,我们可以推导出以下公理:

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

请注意,T1'必须是子类型(或相同类型)T1,而对于T2和,它是相反的T2'.在英语中,这可以解读如下:

函数是另一个函数的子类型如果参数类型的是参数类型的超类型而返回类型是的返回类型的子类型.

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

通过您对共同和逆变的新发现的知识,您应该能够看到以下示例无法编译的原因:

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

问题是A协变,而cons函数期望其类型参数是不变的.因此,A改变了错误的方向.有趣的是,我们可以通过使List逆变来解决这个问题A,但是返回类型List[A]将是无效的,因为cons函数期望其返回类型是协变的.

我们这里唯一的两个选项是:a)使A变量不变,丢失协方差的漂亮,直观的子类型属性,或者b)将一个局部类型参数添加到cons定义A为下界的方法:

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

这现在有效.你可以想象这A是向下变化的,但是B能够向上变化,A因为A它是下限.有了这个方法声明,我们就A可以协变一致,一切顺利.

请注意,只有当我们返回一个List专门针对特定类型较少的类型的实例时,此技巧才有效B.如果你试图使变量成为可能List,那么事情就会崩溃,因为你最终试图将类型的值赋给类型B的变量A,这是编译器不允许的.每当你有可变性时,你需要一个某种类型的mutator,它需要一个特定类型的方法参数,它(与访问器一起)意味着不变性.协方差适用于不可变数据,因为唯一可能的操作是访问器,可以给出协变返回类型.



1> Daniel Spiew..:

通常,协变类型参数是允许在类被子类型时向下变化的参数(或者,随着子类型而变化,因此是"共 - "前缀).更具体地说:

trait List[+A]

List[Int]List[AnyVal]因为Int是子类型的子类型AnyVal.这意味着您可以提供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
}

注意type参数上的" - "方差注释P.这个声明作为一个整体意味着它Function1是逆变的P和协变的R.因此,我们可以推导出以下公理:

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

请注意,T1'必须是子类型(或相同类型)T1,而对于T2和,它是相反的T2'.在英语中,这可以解读如下:

函数是另一个函数的子类型如果参数类型的是参数类型的超类型而返回类型是的返回类型的子类型.

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

通过您对共同和逆变的新发现的知识,您应该能够看到以下示例无法编译的原因:

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

问题是A协变,而cons函数期望其类型参数是不变的.因此,A改变了错误的方向.有趣的是,我们可以通过使List逆变来解决这个问题A,但是返回类型List[A]将是无效的,因为cons函数期望其返回类型是协变的.

我们这里唯一的两个选项是:a)使A变量不变,丢失协方差的漂亮,直观的子类型属性,或者b)将一个局部类型参数添加到cons定义A为下界的方法:

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

这现在有效.你可以想象这A是向下变化的,但是B能够向上变化,A因为A它是下限.有了这个方法声明,我们就A可以协变一致,一切顺利.

请注意,只有当我们返回一个List专门针对特定类型较少的类型的实例时,此技巧才有效B.如果你试图使变量成为可能List,那么事情就会崩溃,因为你最终试图将类型的值赋给类型B的变量A,这是编译器不允许的.每当你有可变性时,你需要一个某种类型的mutator,它需要一个特定类型的方法参数,它(与访问器一起)意味着不变性.协方差适用于不可变数据,因为唯一可能的操作是访问器,可以给出协变返回类型.


这可以用简单的英语说明 - 你可以采取更简单的参数作为参数,你可以返回更复杂的东西吗?
当你提到,"这个规则的原因留给读者一个练习(提示:考虑不同的情况,因为函数是子类型的,就像我上面的数组例子一样)." 你能举几个例子吗?
@perryzheng per [this](http://stackoverflow.com/questions/10603982/contravariance-in-scala),拿`trait Animal`,`trait Cow extends Animal`,`def iNeedACowHerder(牧民:牛=>单位, c:牛)=牧人(c)`和`def iNeedAnAnimalHerder(牧民:动物=>单位,a:动物)=牧人(a)`.然后,`iNeedACowHerder({a:Animal => println("我可以放牧任何动物,包括奶牛")},新牛{})`没关系,因为我们的动物牧民可以放牧牛群,但是`iNeedAnAnimalHerder({c: Cow => println("我只能放牧牛,不是任何动物")},新动物{})`给出编译错误,因为我们的牧牛人不能放牧所有动物.

2> Jatin..:

@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集合都是不变的原因.



3> MarkusQ..:

有关此问题的完整讨论,请参见第57页以上的示例Scala.

如果我正确理解你的评论,你需要重读从第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 

如果您觉得我不理解您的问题(一种明显的可能性),请尝试在问题描述中添加更多解释/上下文,然后我会再试一次.

回应你的编辑:不可变的插槽是一个完全不同的情况......*微笑*我希望上面的例子有所帮助.

推荐阅读
乐韵答题
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有