我正在尝试编译以下代码,但最后一行无法编译:
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限制?有没有办法克服这个问题?
答案 0 :(得分:3)
为了达到你想要的效果,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
is not covariant,您无法在课堂外提供Set[A]
(即您必须删除class MyContainer[+A](ls: Set[A] = Set.empty[A]) // ...
)。如果要访问元素,则必须添加其他方法来查询集合。
Set[A]
<强>更新强>
这是为了更清楚地解释为什么MyContainer[+A]
不能成为class A
class B extends A
公共API的一部分。说我们有:
val x: MyContainer[A] = new MyContainer[B]
想象一下:
val s = x.ls // get internal set
由于协方差,我们可以做到这一点。但是,如果我们现在可以打电话:
s
我们希望Set[A]
的类型为Set
。但是,x
的内部Set[B]
是Set[A]
which is not a {{1}}。因此,输入不正确。