2017-07-02 65 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%的錯誤。