scala 为什么示例不能编译,也就是(co-、contra- 和 in-)方差如何工作?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/663254/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 16:19:17  来源:igfitidea点击:

Why doesn't the example compile, aka how does (co-, contra-, and in-) variance work?

genericsscalacovariancecontravariance

提问by oxbow_lakes

Following on from this question, can someone explain the following in Scala:

this question之后,有人可以在Scala中解释以下内容:

class Slot[+T] (var some: T) { 
   //  DOES NOT COMPILE 
   //  "COVARIANT parameter in CONTRAVARIANT position"

}

I understand the distinction between +Tand Tin 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?

我理解类型声明之间+T和之间的区别T(如果我使用,它会编译T)。但是,实际上如何编写一个在其类型参数中是协变的类而不诉诸于创建unparametrized的东西呢?如何确保以下内容只能使用 的实例创建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 immutableSlotclass which is covariantin its type?

我如何编写一个在其类型上具有变的不可变Slot类?

EDIT 2: Duh! I used varand not val. The following is what I wanted:

编辑 2: 呸!我用过var,没有val。以下是我想要的:

class Slot[+T] (val some: T) { 
}

回答by Daniel Spiewak

Generically, a covarianttype parameter is one which is allowed to vary down as the class is subtyped (alternatively, vary with subtyping, hence the "co-" prefix). More concretely:

一般来说,协变类型参数是一个允许随着类的子类型化而向下变化的参数(或者,随着子类型化而变化,因此是“co-”前缀)。更具体地说:

trait List[+A]

List[Int]is a subtype of List[AnyVal]because Intis 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):

List[Int]是 的子类型List[AnyVal]因为Int是 的子类型AnyVal。这意味着您可以提供一个List[Int]何时需要类型值的实例List[AnyVal]。这确实是泛型工作的一种非常直观的方式,但事实证明,在存在可变数据的情况下使用它是不健全的(破坏了类型系统)。这就是泛型在 Java 中不变的原因。使用 Java 数组(错误地协变)的不健全的简要示例:

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

We just assigned a value of type Stringto 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 ArrayStoreExceptionat runtime. Scala's type system prevents this problem because the type parameter on the Arrayclass is invariant (declaration is [A]rather than [+A]).

我们只是将 type 的值分配给 typeString的数组Integer[]。出于显而易见的原因,这是个坏消息。Java 的类型系统实际上在编译时允许这样做。JVM 将ArrayStoreException在运行时“帮助”抛出一个。Scala 的类型系统防止了这个问题,因为类上的类型参数Array是不变的(声明是[A]而不是[+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 upwardwith 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 Ptype parameter. This declaration as a whole means that Function1is contravariant in Pand covariant in R. Thus, we can derive the following axioms:

请注意类型参数上的“ -”差异注释P。这个声明作为一个整体意味着在 中Function1是逆变P和协变的R。因此,我们可以推导出以下公理:

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 T2and T2'. In English, this can be read as the following:

请注意,T1'必须是 的子类型(或相同类型)T1,而T2和则相反T2'。在英语中,这可以理解为:

A function Ais a subtype of another function Bif the parameter type of Ais a supertype of the parameter type of Bwhile the return type of Ais a subtype of the return type of B.

如果A的参数类型是B的参数类型的超类型,而A的返回类型是B的返回类型的子类型,则函数A是另一个函数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 Ais covariant, while the consfunction expects its type parameter to be invariant. Thus, Ais varying the wrong direction. Interestingly enough, we could solve this problem by making Listcontravariant in A, but then the return type List[A]would be invalid as the consfunction expects its return type to be covariant.

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

Our only two options here are to a) make Ainvariant, losing the nice, intuitive sub-typing properties of covariance, or b) add a local type parameter to the consmethod which defines Aas a lower bound:

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

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

This is now valid. You can imagine that Ais varying downward, but Bis able to vary upward with respect to Asince Ais its lower-bound. With this method declaration, we can have Abe covariant and everything works out.

这是现在有效的。你可以想象它A是向下变化的,但B能够向上变化,A因为A是它的下限。有了这个方法声明,我们就A可以协变了,一切都会好起来的。

Notice that this trick only works if we return an instance of Listwhich is specialized on the less-specific type B. If you try to make Listmutable, things break down since you end up trying to assign values of type Bto 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.

请注意,这个技巧只有在我们返回一个List专门用于不太具体的类型的实例时才有效B。如果您尝试使List可变的,事情就会崩溃,因为您最终试图将 type 的值分配给 typeB的变量A,这是编译器不允许的。每当您有可变性时,您就需要有某种类型的修改器,它需要某种类型的方法参数,这(与访问器一起)意味着不变性。协方差适用于不可变数据,因为唯一可能的操作是访问器,它可以被赋予协变返回类型。

回答by Jatin

@Daniel has explained it very well. But to explain it in short, if it was allowed:

@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.getwill then throw an error at runtime as it was unsuccessful in converting an Animalto Dog(duh!).

slot.get然后将在运行时抛出错误,因为它无法成功转换AnimalDog(废话!)。

In general mutability doesn't go well with co-variance and contra-variance. That is the reason why all Java collections are invariant.

一般来说,可变性不适合协方差和逆方差。这就是所有 Java 集合都是不变的原因。

回答by MarkusQ

See Scala by example, page 57+ for a full discussion of this.

有关此问题的完整讨论,请参见Scala by example,第 57+ 页。

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:

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

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.

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

回答by Saem

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:

您需要对参数应用下限。我很难记住语法,但我认为它看起来像这样:

class Slot[+T, V <: T](var some: V) {
  //blah
}

The Scala-by-example is a bit hard to understand, a few concrete examples would have helped.

Scala-by-example 有点难以理解,一些具体的例子会有所帮助。