为什么这个例子没有编译,又如何(合作,反对和in)方差工作? [英] Why doesn't the example compile, aka how does (co-, contra-, and in-) variance work?

查看:105
本文介绍了为什么这个例子没有编译,又如何(合作,反对和in)方差工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  class Slot [+ T](var some:T){
//不编译
//在CONTRAVARIANT位置的COVARIANT参数

}

我了解 + T T 之间的区别类型声明(它会在我使用 T 时进行编译)。但是,那么如何实际编写一个类型参数协变的类而不用创建这个东西?我如何确保只能使用 T 实例创建以下内容?

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

编辑 - 现在将其归结为以下内容:

 抽象类_Slot [+ T,V<:T](var some:V){
def getT()= {some}

$ / code>

这很好,但我现在有两个类型参数,我只想要一个。我会重新提出这样的问题:

我该如何写一个不可变的 Slot code>类是 covariant 在它的类型中



编辑2 :Duh !我使用 var 而不是 val 。以下是我想要的:
$ b $ pre $ class Slot [+ T](val some:T){
}


解决方案

一般来说,一个协变类型参数是一个允许随着类的子类型而变化的参数(或者,根据子类型变化,因此是共同前缀)。更具体地说:

 特质列表[+ A] 

List [Int] List的子类型[AnyVal] 因为 Int AnyVal 的子类型。这意味着当预期类型为 List [AnyVal] 的值时,您可以提供一个 List实例[Int] 。这对于泛型是非常直观的工作方式,但是在存在可变数据的情况下使用时,它会变得不健全(打破类型系统)。这就是泛型在Java中不变的原因。使用Java数组(这是错误的协变)的不稳定的简单例子:

  Object [] arr = new Integer [1]; 
arr [0] =你好,那里!;

我们刚刚分配了一个类型值 String 转换为 Integer [] 类型的数组。由于原因应该很明显,这是一个坏消息。 Java的类型系统实际上是在编译时允许的。 JVM将在运行时帮助抛出 ArrayStoreException 。 Scala的类型系统可以防止这个问题,因为 Array 类的类型参数是不变的(声明是 [A] ,而不是 [+ A] )。



请注意,还有另一种类型的方差称为 contravariance EM>。这非常重要,因为它解释了为什么协方差会导致一些问题。逆向变换实际上与协方差相反:参数随着子类型的不同而向上变化。部分原因是它不那么常见,因为它非常直观,但它确实有一个非常重要的应用程序:函数。

  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 的参数类型是 B 的参数类型的超类型,而 A 的返回类型是 B B 的返回类型的子类型。

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

利用你新发现的协变和逆变知识,你应该能够看到为什么下面的例子不会编译:

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

问题在于 A 是协变的,而 cons 函数期望它的类型参数是不变的。因此, A 正在改变错误的方向。有意思的是,我们可以通过在 A 中使 List逆转来解决这个问题,但是返回类型列表[A] 将是无效的,因为 cons 函数期望它的返回类型为 covariant 。 p>

这里我们唯一的两个选择是a)使 A 不变,失去了良好,直观的子协变性,或者b)向 cons 方法中添加一个本地类型参数,该方法定义 A 作为下限:

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

现在这是有效的。你可以想象 A 向下变化,但是 B 可以相对于 A ,因为 A 是它的下限。通过这个方法声明,我们可以让 A 是协变的,一切都可以实现。



注意这个技巧只适用于如果我们返回一个 List 的实例,该实例专用于较少特定的类型 B 。如果您尝试使 List 可变,则最终会尝试将类型 B 的值分配给类型为 A 的变量,这是编译器不允许的。每当你有可变性时,你需要有一个某种类型的增变器,它需要一个特定类型的方法参数,它和访问器一起意味着不变性。协变性适用于不可变数据,因为唯一可能的操作是一个访问器,它可以被赋予一个协变返回类型。


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) { 
}

解决方案

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:

trait List[+A]

List[Int] is a subtype of List[AnyVal] because Int is a subtype of AnyVal. This means that you may provide an instance of List[Int] when a value of type List[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):

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

We just assigned a value of type String to an array of type Integer[]. 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 an ArrayStoreException at runtime. Scala's type system prevents this problem because the type parameter on the Array 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.

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

Notice the "-" variance annotation on the P type parameter. This declaration as a whole means that Function1 is contravariant in P and covariant in R. Thus, we can derive the following axioms:

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

Notice that T1' must be a subtype (or the same type) of T1, whereas it is the opposite for T2 and T2'. In English, this can be read as the following:

A function A is a subtype of another function B if the parameter type of A is a supertype of the parameter type of B while the return type of A is a subtype of the return type of B.

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:

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

The problem is that A is covariant, while the cons function expects its type parameter to be invariant. Thus, A is varying the wrong direction. Interestingly enough, we could solve this problem by making List contravariant in A, but then the return type List[A] would be invalid as the cons 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 the cons method which defines A as a lower bound:

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

This is now valid. You can imagine that A is varying downward, but B is able to vary upward with respect to A since A is its lower-bound. With this method declaration, we can have A 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 type B. If you try to make List mutable, things break down since you end up trying to assign values of type B to a variable of type A, 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.

这篇关于为什么这个例子没有编译,又如何(合作,反对和in)方差工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆