继这个问题之后,有人可以在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,它需要一个特定类型的方法参数,它(与访问器一起)意味着不变性.协方差适用于不可变数据,因为唯一可能的操作是访问器,可以给出协变返回类型.
通常,协变类型参数是允许在类被子类型时向下变化的参数(或者,随着子类型而变化,因此是"共 - "前缀).更具体地说:
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,它需要一个特定类型的方法参数,它(与访问器一起)意味着不变性.协方差适用于不可变数据,因为唯一可能的操作是访问器,可以给出协变返回类型.
@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
(duh!).
一般来说,变异性与协方差和反方差不相符.这就是为什么所有Java集合都是不变的原因.
有关此问题的完整讨论,请参见第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
如果您觉得我不理解您的问题(一种明显的可能性),请尝试在问题描述中添加更多解释/上下文,然后我会再试一次.
回应你的编辑:不可变的插槽是一个完全不同的情况......*微笑*我希望上面的例子有所帮助.