为什么该示例无法编译,即(同、对、内)方差如何工作?
继这个问题之后,有人可以在 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
类,其类型协变?< /strong>
编辑 2:呃! 我使用了 var
而不是 val
。 以下是我想要的:
class Slot[+T] (val some: T) {
}
Following on from this question, can someone explain the following in Scala:
class Slot[+T] (var some: T) {
// DOES NOT COMPILE
// "COVARIANT parameter in CONTRAVARIANT position"
}
I understand the distinction between +T
and T
in the type declaration (it compiles if I use T
). But then how does one actually write a class which is covariant in its type parameter without resorting to creating the thing unparametrized? How can I ensure that the following can only be created with an instance of T
?
class Slot[+T] (var some: Object){
def get() = { some.asInstanceOf[T] }
}
EDIT - now got this down to the following:
abstract class _Slot[+T, V <: T] (var some: V) {
def getT() = { some }
}
this is all good, but I now have two type parameters, where I only want one. I'll re-ask the question thus:
How can I write an immutable Slot
class which is covariant in its type?
EDIT 2: Duh! I used var
and not val
. The following is what I wanted:
class Slot[+T] (val some: T) {
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
一般来说,协变类型参数是允许随着类的子类型化而向下变化的类型参数(或者,随着子类型化而变化,因此有“co-”前缀)。 更具体地说:
List[Int]
是List[AnyVal]
的子类型,因为Int
是AnyVal
的子类型。 这意味着当需要List[AnyVal]
类型的值时,您可以提供List[Int]
的实例。 对于泛型来说,这确实是一种非常直观的工作方式,但事实证明,当在存在可变数据的情况下使用时,它是不健全的(破坏了类型系统)。 这就是为什么泛型在 Java 中是不变的。 使用 Java 数组(错误地协变)的不健全性的简单示例:我们刚刚将
String
类型的值分配给Integer[]
类型的数组。 出于显而易见的原因,这是个坏消息。 Java 的类型系统实际上在编译时允许这样做。 JVM 将在运行时“有效地”抛出 ArrayStoreException。 Scala 的类型系统可以防止此问题,因为Array
类上的类型参数是不变的(声明是[A]
而不是[+A]
) 。请注意,还有另一种类型的方差,称为逆变。 这非常重要,因为它解释了为什么协方差会导致一些问题。 逆变实际上与协变相反:参数随着子类型的变化向上。 它不太常见,部分原因是它非常违反直觉,尽管它确实有一个非常重要的应用:函数。
请注意
P
类型参数上的“-”差异注释。 该声明作为一个整体意味着Function1
在P
中是逆变的,在R
中是协变的。 因此,我们可以推导出以下公理:注意,
T1'
必须是T1
的子类型(或相同类型),而T2 则相反
和T2'
。 在英语中,这可以读作如下:这条规则的原因留给读者作为练习(提示:考虑不同的情况,因为函数是子类型的,就像上面的数组示例一样)。
凭借您新发现的协变和逆变知识,您应该能够明白为什么以下示例无法编译:
问题是
A
是协变的,而cons
> 函数期望其类型参数不变。 因此,A
改变了错误的方向。 有趣的是,我们可以通过在A
中使List
逆变来解决这个问题,但是返回类型List[A]
将无效,因为cons
函数期望其返回类型为协变。这里我们唯一的两个选择是 a) 使 A 不变,失去协方差的良好、直观的子类型属性,或者 b) 将本地类型参数添加到 cons 中将
A
定义为下限的方法:现在有效。 您可以想象
A
向下变化,但B
能够相对于A
向上变化,因为A
是它的下界。 通过这个方法声明,我们可以让A
协变并且一切正常。请注意,只有当我们返回专门针对不太特定的类型
B
的List
实例时,此技巧才有效。 如果您尝试使List
可变,事情就会崩溃,因为您最终尝试将B
类型的值分配给A
类型的变量,这是编译器不允许的。 每当你具有可变性时,你就需要有某种类型的增变器,这需要某种类型的方法参数,这(与访问器一起)意味着不变性。 协变适用于不可变数据,因为唯一可能的操作是访问器,它可以被赋予协变返回类型。Generically, a covariant type parameter is one which is allowed to vary down as the class is subtyped (alternatively, vary with subtyping, hence the "co-" prefix). More concretely:
List[Int]
is a subtype ofList[AnyVal]
becauseInt
is a subtype ofAnyVal
. This means that you may provide an instance ofList[Int]
when a value of typeList[AnyVal]
is expected. This is really a very intuitive way for generics to work, but it turns out that it is unsound (breaks the type system) when used in the presence of mutable data. This is why generics are invariant in Java. Brief example of unsoundness using Java arrays (which are erroneously covariant):We just assigned a value of type
String
to an array of typeInteger[]
. For reasons which should be obvious, this is bad news. Java's type system actually allows this at compile time. The JVM will "helpfully" throw anArrayStoreException
at runtime. Scala's type system prevents this problem because the type parameter on theArray
class is invariant (declaration is[A]
rather than[+A]
).Note that there is another type of variance known as contravariance. This is very important as it explains why covariance can cause some issues. Contravariance is literally the opposite of covariance: parameters vary upward with subtyping. It is a lot less common partially because it is so counter-intuitive, though it does have one very important application: functions.
Notice the "-" variance annotation on the
P
type parameter. This declaration as a whole means thatFunction1
is contravariant inP
and covariant inR
. Thus, we can derive the following axioms:Notice that
T1'
must be a subtype (or the same type) ofT1
, whereas it is the opposite forT2
andT2'
. In English, this can be read as the following:The reason for this rule is left as an exercise to the reader (hint: think about different cases as functions are subtyped, like my array example from above).
With your new-found knowledge of co- and contravariance, you should be able to see why the following example will not compile:
The problem is that
A
is covariant, while thecons
function expects its type parameter to be invariant. Thus,A
is varying the wrong direction. Interestingly enough, we could solve this problem by makingList
contravariant inA
, but then the return typeList[A]
would be invalid as thecons
function expects its return type to be covariant.Our only two options here are to a) make
A
invariant, losing the nice, intuitive sub-typing properties of covariance, or b) add a local type parameter to thecons
method which definesA
as a lower bound:This is now valid. You can imagine that
A
is varying downward, butB
is able to vary upward with respect toA
sinceA
is its lower-bound. With this method declaration, we can haveA
be covariant and everything works out.Notice that this trick only works if we return an instance of
List
which is specialized on the less-specific typeB
. If you try to makeList
mutable, things break down since you end up trying to assign values of typeB
to a variable of typeA
, which is disallowed by the compiler. Whenever you have mutability, you need to have a mutator of some sort, which requires a method parameter of a certain type, which (together with the accessor) implies invariance. Covariance works with immutable data since the only possible operation is an accessor, which may be given a covariant return type.@Daniel已经解释得很好了。 但简而言之,如果允许:
slot.get
将在运行时抛出错误,因为它未能成功地将Animal
转换为Dog< /代码>(呃!)。
一般来说,可变性与协变和逆变不太相配。 这就是所有 Java 集合都是不变的原因。
@Daniel has explained it very well. But to explain it in short, if it was allowed:
slot.get
will then throw an error at runtime as it was unsuccessful in converting anAnimal
toDog
(duh!).In general mutability doesn't go well with co-variance and contra-variance. That is the reason why all Java collections are invariant.
请参阅 Scala 示例,第57+对此进行了全面讨论。
如果我正确理解你的评论,你需要重新阅读从第 56 页底部开始的段落(基本上,我认为你所要求的在没有运行时检查的情况下不是类型安全的,scala 不这样做,所以你运气不好)。 翻译他们的示例以使用您的构造:
如果您觉得我不理解您的问题(一种明显的可能性),请尝试在问题描述中添加更多解释/上下文,我会再试一次。
回应您的编辑:不可变插槽是一种完全不同的情况...*微笑*我希望上面的示例有所帮助。
See Scala by example, page 57+ for a full discussion of this.
If I'm understanding your comment correctly, you need to reread the passage starting at the bottom of page 56 (basically, what I think you are asking for isn't type-safe without run time checks, which scala doesn't do, so you're out of luck). Translating their example to use your construct:
If you feel I'm not understanding your question (a distinct possibility), try adding more explanation / context to the problem description and I'll try again.
In response to your edit: Immutable slots are a whole different situation...* smile * I hope the example above helped.
您需要对参数应用下限。 我很难记住语法,但我认为它看起来像这样:
Scala-by-example 有点难以理解,一些具体的例子会有所帮助。
You need to apply a lower bound on the parameter. I'm having a hard time remembering the syntax, but I think it would look something like this:
The Scala-by-example is a bit hard to understand, a few concrete examples would have helped.