Scala协变文档示例允许苹果和橙子

时间:2017-07-02 16:32:58

标签: scala covariance

请参阅此实现遵循上限示例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列表

2 个答案:

答案 0 :(得分:1)

可变列表在其参数类型中不能/不应该是协变的。 正是因为你注意到的原因。

假设您可以拥有MutableList[Orange],这是MutableList[Fruit]的子类。现在,没有任何东西可以阻止你创建一个函数:

 def putApple(fruits: MutableList[Fruit], idx: Int) = 
     fruits(idx) = new Apple

您可以将Apple添加到Fruits列表中,因为AppleFruit,这没有任何问题。 但是一旦你有这样的功能,就没有理由你不能这样称呼它:

 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]
}