我有以下课程:
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 <: Animal
和Cat <: 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
.
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
是一个下限,告诉编译器在必要时推断出一个超类型.正如在示例中可以看到的那样,Animal
在Cat
给出时是推断的.
尝试理解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)
基本上你不能将A
s 放入 ifA
是协变的,你只能将其取出(例如:returning A
)。如果你想把A
s 放进去,那么你需要制作它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)
最好的方法是保持其协变并摆脱设置器并采用不可变的方法。