2017-07-02 57 views
0

看到这个实现苹果和桔子遵循上限例如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?

User @gábor-bakos指出我混淆了不变性与协变性。所以我尝试了可变列表缓冲区。它并不后来让苹果被插入到橙色清单缓冲器,但它不是协变

val ll : ListBuffer[BigOrange]= ListBuffer(bo) 
    ll += bo //good 
    ll += a // not allowed 

So..can我上面的例子(ListNode)已被修改,以便 1.它是协变(它是) 二是可变的,但可变像ListBuffer例子(以后不会允许苹果被插入BigOrange列表

+0

我不确定你的意思/问。你想要一个协变列表的行为像一个不变列表吗? –

+0

嗯,好的。所以协方差和不变性是2个不同的概念。我怎样才能让这个例子不变?具体来说,我该如何添加一个推断类型的构造函数,即编译器会将以下内容标记为无效: – user7938511

+1

以使其不变,并在B之前移除“+”号。另外,在'prepend'中放宽类型参数,并使参数类型为'B'。 – Dima

回答

0

,你很可能缺少的主要事情是prepend不修改的列表。在线路val foo2 : ListNode[Orange] = foo列表foo仍然是ListNode[BigOrange]类型,并且给定参数的协方差,此分配有效(和对此没有特别的尴尬)。编译器会阻止您指定FruitsOranges(在这种情况下分配的AppleOrange是危险的),但你必须事先保存修改单:

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

返回类型实际上应该是'Node [U]'(此时ListNode尚未定义)。 –

+0

@Kombajnzbożowy你是对的,已经修复了它 – greenshade

+0

它可以是'ListNode [U]',它只有在您逐个输入REPL中的定义时才会起作用。 –

1

一个可变的列表不能/不应该在它的参数类型协变的。正是因为你提到的原因。

假设您可能有MutableList[Orange],这是MutableList[Fruit]的子类。现在,有什么能阻止你做一个函数:

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

您可以添加到AppleFruits名单,因为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%的错误。