1. 程式人生 > >Scala類型系統(sudden thought)

Scala類型系統(sudden thought)

cal nim gen eve 設有 tro 一段 pen cnblogs

  http://docs.scala-lang.org/tour/lower-type-bounds.html中有一段代碼

trait Node[+B] {
  def prepend(elem: B): Unit
}

case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
  def prepend(elem: B) = ListNode[B](elem, this)
  def head: B = h
  def tail = t
}

case class Nil[+B]() extends Node[B] {
  def prepend(elem: B) = ListNode[B](elem, this)
}

  文中說這段代碼不會通過編譯,因為Function1是contravariant 在參數的位置上。看到這裏是一個頭很多個大的。 However, this program does not compile because the parameter elem in prepend is of type B, which we declared covariant. This doesn’t work because functions are contravariant in their parameter types and covariant in their result types.

  先假設一下如果能編譯通過的話。

  假設有這樣子的一段代碼

trait Animal
case class Dog() extends Animal
case class Cat() extends Animal
function addDogToAnimal(animalNode : ListNode[Animal]) : Unit{
    animalNode.prepend(Dog())
}
如果generic的類型是Animal的話,ListNode就變成如下
case class ListNode[Animal](h: Animal, t: Node[Animal]) extends Node[Animal] { def prepend(elem: Animal) = ListNode[Animal](elem, this) def head: Animal = h def tail = t }
如果generic的類型是Cat的話,ListNode就變成如下
case class ListNode[Cat](h:Cat, t: Node[Cat]) extends Node[Cat] { def prepend(elem:Cat) = ListNode[Cat](elem, this) def head: Cat= h def tail = t }

 addDogToAnimal方法接受一個ListNode[Animal],因為ListNode[Cat] 是 ListNode[Animal]的子類(因為是Covaraiance的)

 所以我們可以addDogToAnimal(ListNode(Cat(), Nil())),但是ListNode[Cat]只能prepend是Cat類型的對象。所以一定會出問題。

 解決方法就是在所有需要消費者方法中 introducing a new type parameter U that has B as a lower type bound.

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)
}
現在再來看剛才的問題

如果generic的類型是Animal的話,ListNode就變成如下
case class ListNode[Animal](h: Animal, t: Node[Animal]) extends Node[Animal] {
  def prepend[U >: Animal](elem: Animal) = ListNode[Animal](elem, this)
  def head: Animal = h
  def tail = t
}
如果generic的類型是Cat的話,ListNode就變成如下
case class ListNode[Cat](h:Cat, t: Node[Cat]) extends Node[Cat] { def prepend[U >: Cat](elem:Cat) = ListNode[Cat](elem, this) def head: Cat= h def tail = t }
ListNode[Cat] 還是 ListNode[Animal]的子類
addDogToAnimal(ListNode(Cat(), Nil()))的時候
ListNode[Cat]的prepend方法可以接受所有U >: Cat 的對象
所以prepend方法可以接受Animal的對象作為參數。Dog也是一種Animal,所以
animalNode.prepend(Dog())是沒有問題的
在這裏以一個java開發者來說,會覺得很不合理。明明是cat類型的ListNode,怎麽可以加入dog。但這也是scala和java的不同呀。唉


  

Scala類型系統(sudden thought)