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

gudnpqoy  于 2023-03-08  发布在  Scala
关注(0)|答案(5)|浏览(146)

我有以下类:

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

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

}

编译器抱怨:

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

我搜索了很多关于这个错误的信息,但是找不到有用的东西来帮助我理解这个错误。
有人能解释一下为什么会出现错误吗?

wpx232ag

wpx232ag1#

一旦你理解了错误信息,它实际上是非常清楚的。让我们一起来看看。
你在类A的类型参数A中声明类Box为协变量,这意味着对于扩展A的任何类型X(即X <: A),Box[X]都可以看作是Box[A]
为了给予一个清晰的例子,让我们考虑Animal类型:

sealed abstract class Animal
case class Cat extends Animal
case class Dog extends Animal

如果你定义了Dog <: AnimalCat <: Animal,那么Box[Dog]Box[Cat]都可以看作Box[Animal],你可以创建一个包含这两种类型的集合,并保留Box[Animal]类型。
虽然这个属性在某些情况下非常方便,但是它也对Box上可用的操作施加了约束,这就是编译器不允许定义def set的原因。
如果你允许定义

def set(a:A): Unit

则以下代码有效:

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: if `set` mutates `animalBox`, it also mutates `catBox` to no longer contain a `Cat` but a `Dog`

最后一行显然是个问题,因为catBox现在将包含一个Dog!方法的参数出现在所谓的“逆变位置”,这与协方差相反。实际上,如果你定义Box[-A],那么Cat <: Animal就意味着Box[Cat] >: Box[Animal]Box[Cat]Box[Animal]的超类型)。对于我们的例子,这当然是没有意义的。
您的问题的一个解决方案是使Box类不可变(例如,不提供任何方式来更改Box的内容),而是使用在您的case class伙伴中定义的apply方法来创建新框。您还可以在本地定义set,并且通过将其声明为private[this]而不在Box之外的任何地方公开它。编译器将允许这样做,因为private[this]保证了错误示例的最后一行不会被编译,因为set方法在Box的特定示例之外是完全不可见的。
如果由于某种原因,您不想使用apply方法创建新示例,也可以按如下方式定义set

def set[B >: A](b: B): Box[B] = Box(b)
muk1a3rh

muk1a3rh2#

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

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

类型参数B >: A是一个下界,它告诉编译器在必要时推断出一个超类型,正如在示例中可以看到的,当给定Cat时,Animal被推断出。

cvxl0en2

cvxl0en23#

试着理解你的Box[+A]A中协变意味着什么:
这意味着Box[Dog]也应该是Box[Animal],因此Box[Dog]的任何示例都应该具有Box[Animal]具有的所有方法。
特别是,Box[Dog]应该具有方法

set(a: Animal): Box[Animal]

但是,它只有一个方法

set(a: Dog): Box[Dog]

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

2fjabf4q

2fjabf4q4#

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

def set[B >: A](x: B): Box[B] = Box(x)
vkc1a9a2

vkc1a9a25#

基本上,如果A是协变的,则不能将A放入,只能将其取出(例如:返回A)。如果希望放入A s,则需要将其设置为contravariant

case class Box[-A](value: A)

你想两个都做吗,那就让它不变

case class Box[A](value: A)

最好的方法是保持它的协变性,去掉setter,采用一种不变的方法。

相关问题