Scala协方差误差



我试图编译以下代码,但最后一行没有编译:

class SuperContainer (
  val shapeSets: Set[MyContainer[Shape]] = Set.empty[MyContainer[Shape]]) {
  def addAct(el: MyContainer[Shape]) = {
    new SuperContainer(shapeSets + el)
  }
}
class MyContainer[A](val ls: Set[A] = Set.empty[A]) {
  def addElement(el: A) = {
    new MyContainer(ls + el)
  }
}  

abstract class Shape
case class Circle(radius: Int) extends Shape {
  override def toString = "Circle(" + radius + ")"
}
case class Square(s: Int) extends Shape {
  override def toString = "Square(" + s + ")"
}

object MyContainer {
  def main(args: Array[String]) {
    //Circle Container
    val myc1 = new MyContainer[Circle]()
    val myc11 = myc1.addElement(new Circle(6))
    //Square Container
    val myc2 = new MyContainer[Square]()
    val myc21 = myc2.addElement(new Square(6))

    val scont = new SuperContainer
    scont.addAct(myc11) //does not compile
  }
}

Scala编译器建议我在MyContainer类定义中使用+A,但这样做会出现其他编译错误。我是做错了什么,还是这只是Scala的限制?有什么办法可以克服这个问题吗?

为了实现您想要的,MyContainer必须是协变的:

class MyContainer[+A](val ls: Set[A] = Set.empty[A]) // ...

现在,您对addElement的定义将导致一个错误,因为A出现在相反的位置(在本例中作为函数参数)。您必须对您的签名进行如下调整:

def addElement[B >: A](el: B): MyContainer[B]

如果你想一想,这是有道理的:如果你有一个Container[Circle](由于协方差,它可以被视为Container[Shape]),并且你添加了一个Shape,那么你在结尾有一个Container[Shape],而不是一个Container[Circle]

addElement的实施不会改变。

此外,不能使Set[A]在类之外可用(即,必须删除val),因为Set[A]不是协变的。如果要访问元素,则必须添加其他方法来查询集合。

class MyContainer[+A](ls: Set[A] = Set.empty[A]) // ...

更新

这是为了更清楚地解释为什么Set[A]不能成为MyContainer[+A]的公共API的一部分。假设我们有:

class A
class B extends A

想象一下:

val x: MyContainer[A] = new MyContainer[B]

由于协方差,我们可以做到这一点。然而,如果我们现在可以调用:

val s = x.ls // get internal set

我们期望sSet[A]类型。然而,x的内部Set而不是Set[A]Set[B]。因此,这不是正确键入的。

最新更新