请参阅此实现遵循上限示例http://docs.scala-lang.org/tutorials/tour/upper-type-bounds.html
class Fruit(name: String)
class Apple (name: String) extends Fruit(name)
class Orange(name: String) extends Fruit(name)
class BigOrange(name:String) extends Orange(name)
class BigFLOrange(name:String) extends BigOrange(name)
// Straight from the doc
trait Node[+B ] {
def prepend[U >: B ](elem: U)
}
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend[U >:B ](elem: U) = ListNode[U](elem, this)
def head: B = h
def tail = t
}
case class Nil[+B ]() extends Node[B] {
def prepend[U >: B ](elem: U) = ListNode[U](elem, this)
}
但是这个定义似乎允许在同一个容器中存在多个不相关的东西
val f = new Fruit("fruit")
val a = new Apple("apple")
val o = new Orange("orange")
val bo = new BigOrange("big orange")
val foo :ListNode[BigOrange] = ListNode[BigOrange](bo, Nil())
foo.prepend(a) // add an apple to BigOrangeList
foo.prepend(o) // add an orange to BigOrangeList
val foo2 : ListNode[Orange] = foo // and still get to assign to OrangeList
所以我不确定这是文档中的一个很好的例子。并且,问题,我如何修改约束,以便...这更像是List?
用户@gábor-bakos指出我对协方差的不变性感到困惑。所以我尝试了可变列表缓冲区。它后来不允许将苹果插入橙色列表缓冲区,但它不是协变的
val ll : ListBuffer[BigOrange]= ListBuffer(bo)
ll += bo //good
ll += a // not allowed
所以......我上面的例子(ListNode)可以修改 1.它是协变的(它已经是) 2它是可变的,但像ListBuffer示例一样可变(以后不允许将苹果插入BigOrange列表
答案 0 :(得分:1)
可变列表在其参数类型中不能/不应该是协变的。 正是因为你注意到的原因。
假设您可以拥有MutableList[Orange]
,这是MutableList[Fruit]
的子类。现在,没有任何东西可以阻止你创建一个函数:
def putApple(fruits: MutableList[Fruit], idx: Int) =
fruits(idx) = new Apple
您可以将Apple
添加到Fruits
列表中,因为Apple
是Fruit
,这没有任何问题。
但是一旦你有这样的功能,就没有理由你不能这样称呼它:
val oranges = new MutableList[Orange](new Orange, new Orange)
putApple(oranges, 0)
这将编译,因为MutableList[Orange]
是MutableList[Fruit]
的子类。但现在:
val firstOrange: Orange = oranges(0)
会崩溃,因为oranges
的第一个元素实际上是Apple
。
出于这个原因,可变集合必须在元素类型中保持不变(回答您在评论中提出的问题,使列表不变,在+
之前删除B
,并获得摆脱prepend
中的类型参数。它应该只是def pretend(elem: B)
)。
如何绕过它?最好的解决方案是不使用可变集合。您不应该在99%或现实生活中使用scala代码。如果你认为你需要一个,那么99%你做错了。
答案 1 :(得分:0)
您可能遗漏的主要事情是prepend
不会修改列表。在行val foo2 : ListNode[Orange] = foo
中,列表foo
仍然是ListNode[BigOrange]
类型,并且给出了参数的协方差,这个赋值是有效的(并且没有什么特别尴尬)。编译器会阻止您将Fruits
分配给Oranges
(在这种情况下,将Apple
分配给Orange
是危险的)但您必须事先保存修改后的列表:
val foo: ListNode[Fruit] = ListNode[BigOrange](bo, Nil()).prepend(a).prepend(o)
val foo2: ListNode[Orange] = foo // this is not valid
此外,Node
定义缺少prepend
的返回类型 - 因此编译器会推断错误的返回类型(Unit
而不是ListNode[U]
)。
这是固定版本:
trait Node[+B] {
def prepend[U >: B ](elem: U): Node[U]
}