为什么不例子编译,又名怎么做(合作,禁忌,和IN)方差的工作?
-
20-08-2019 - |
题
这这个问题时,有人可以解释以下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 强>:咄!我用var
而不是val
。下面是我想要的东西:
class Slot[+T] (val some: T) {
}
解决方案
一般地,一协变型参数是一个被允许作为类亚型改变向下(或者,变化与子类型,因而有“共”前缀)。更具体地:
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
。因为在Array
类类型参数是不变的(声明[A]
而非[+A]
)Scala的类型系统防止了这种问题。
请注意,有被称为逆变方差的另一种类型。这是非常重要的,因为它解释了为什么协方差可能会导致一些问题。逆变是字面上协方差的相反:参数变化的向上与子类型。它是少了很多共同部分,因为它是如此反直觉,虽然它确实有一个非常重要的应用:功能
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 的是另一种功能的乙的一个子类型,如果的参数类型的 A 的是参数类型的乙而返回类型 A 的是的返回类型的子类型的乙
这样做的原因规则被留给读者作为练习给读者。(提示:考虑不同的情况下,作为功能亚型,如我的阵列例如从上方)
使用你的新发现合作和逆变的知识,你应该能够明白为什么下面的例子将不能编译:
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
是协变,一切工作了。
请注意,THI把戏,只有当我们回到这是专门对无特定类型List
B
的实例作品。如果你试图让List
可变的,东西打破,因为你最终要类型B
的值赋给类型A
,这是由编译器不允许的变量。只要你有可变性,你需要有某种形式的,这需要某种类型,这(与存取一起)意味着不变性的方法参数的赋值函数。协方差可与不可变的数据,因为唯一可能的操作是一个访问器,其可被给定协变返回类型。
其他提示
@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
将在运行时引发错误,因为它是在一个转换到Animal
Dog
(杜!)不成功的。
在一般的可变性不具有共方差和反方差顺利。这就是为什么所有的Java集合是不变的原因。
请参阅 Scala中通过示例一>,57+页的这个完整的讨论。
如果我正确理解您的意见,您需要重新读取通道起始于56页(基本上是底部,我认为你所要求的是不是类型安全无运行时检查,这斯卡拉没有按”做T,所以你的运气了)。平移其例如使用您的构建体:
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
如果你觉得我不理解你的问题(一个明显的可能性),尝试加入更多的解释/上下文问题的描述,我会再试一次。
在回答您的编辑:不可变槽是一个完全不同的局面... *笑*我希望上面的帮助例子
您需要申请一个下界参数。我有一个很难记住语法,但我认为这会是这个样子:
class Slot[+T, V <: T](var some: V) {
//blah
}
在Scala的按示例是有点难以理解,几个具体的例子会帮助。