协变类型A出现在值a的类型A的逆变位置

zer*_*ing 25 scala

我有以下课程:

case class Box[+A](value: A) {

  def set(a: A): Box[A] = Box(a)

}
Run Code Online (Sandbox Code Playgroud)

并且编译器抱怨:

Error:(4, 11) covariant type A occurs in contravariant position in type A of value a
  def set(a: A): Box[A] = Box(a)
Run Code Online (Sandbox Code Playgroud)

我正在搜索关于错误的很多内容,但找不到有助于我理解错误的有用内容.

有人可以解释,为什么会发生错误?

fra*_*isr 36

一旦你理解它,错误信息实际上非常清楚.我们一起来吧.

Box在类型参数中将类声明为协变A.这意味着对于任何类型的X扩展A(即X <: A),Box[X]都可以看作是Box[A].

为了给出一个明确的例子,让我们考虑一下这种Animal类型:

sealed abstract class Animal
case class Cat extends Animal
case class Dog extends Animal
Run Code Online (Sandbox Code Playgroud)

如果定义Dog <: AnimalCat <: Animal,然后双方Box[Dog]Box[Cat]可以看作是Box[Animal],你可以如创建包含两种类型单一的收集和保存Box[Animal]类型.

虽然在某些情况下此属性非常方便,但它也会对您可以使用的操作施加限制Box.这就是编译器不允许您定义的原因def set.

如果你允许定义

def set(a:A): Unit
Run Code Online (Sandbox Code Playgroud)

那么我以下代码是有效的:

val catBox = new Box[Cat]
val animalBox: Box[Animal] = catBox // valid because `Cat <: Animal`
val dog = new Dog
animalBox.set(dog) // This is non-sensical!
Run Code Online (Sandbox Code Playgroud)

最后一行显然是一个问题,因为catBox现在将包含一个Dog!方法的参数出现在所谓的"逆变位置"中,这与协方差相反.实际上,如果你定义Box[-A],那么Cat <: Animal暗示Box[Cat] >: Box[Animal](Box[Cat]是一种超类型Box[Animal]).对于我们的例子,这当然是非感性的.

您的问题的一个解决方案是使Box类不可变(即不提供任何方式来更改a的内容Box),而是使用case class随附的apply方法定义来创建新框.如果需要,您还可以set在本地定义,而不是Box通过声明它来将其暴露在外面的任何地方private[this].编译器将允许这样做,因为private[this]保证我们的错误示例的最后一行不会编译,因为该set方法在特定实例之外是完全不可见的Box.

  • “Box[Cat]”如何成为“Box[Animal]”的超类型?现实世界中的猫不可能是动物的超类。 (2认同)

kir*_*uku 17

其他人已经给出了为什么代码不能编译的答案,但他们没有给出如何编译代码的解决方案:

> case class Box[+A](v: A) { def set[B >: A](a: B) = Box(a) }
defined class Box
> trait Animal; case class Cat() extends Animal
defined trait Animal
defined class Cat
> Box(Cat()).set(new Animal{})
res4: Box[Animal] = Box($anon$1@6588b715)
> Box[Cat](Cat()).set[Animal](new Animal{})
res5: Box[Animal] = Box($anon$1@1c30cb85)
Run Code Online (Sandbox Code Playgroud)

type参数B >: A是一个下限,告诉编译器在必要时推断出一个超类型.正如在示例中可以看到的那样,AnimalCat给出时是推断的.


Cyr*_*pet 7

尝试理解Box[+A]协变对你来说意味着什么A对您来说意味着什么:

这意味着 aBox[Dog]也应该是 a Box[Animal],因此 的任何实例都Box[Dog]应该具有 a 所具有的所有方法Box[Animal]

特别是, aBox[Dog]应该有一个方法

set(a: Animal): Box[Animal]
Run Code Online (Sandbox Code Playgroud)

然而它只有一个方法

set(a: Dog): Box[Dog]
Run Code Online (Sandbox Code Playgroud)

现在,您可能认为可以从第二个签名推断出第一个签名,但事实并非如此:您想Cat仅使用第二个签名来对 a 进行装箱吗?这是不可行的,这就是编译器告诉您的:方法中的参数是逆变位置(您只能放置逆变(或不变)类型参数)。


小智 6

除了其他答案,我想提供另一种方法:

def set[B >: A](x: B): Box[B] = Box(x)
Run Code Online (Sandbox Code Playgroud)


Sle*_*idi 5

基本上你不能将As 放入 ifA是协变的,你只能将其取出(例如:returning A)。如果你想把As 放进去,那么你需要制作它contravariant

case class Box[-A](value: A)
Run Code Online (Sandbox Code Playgroud)

你想两者都做吗,然后让它保持不变

case class Box[A](value: A)
Run Code Online (Sandbox Code Playgroud)

最好的方法是保持其协变并摆脱设置器并采用不可变的方法。