本文由 Yison 發表在 ScalaCool 團隊部落格。
CanBuildFrom is probably the most infamous abstraction of the current collections. It is mainly criticised for making scary type signatures.
從 上一篇文章 看出, CanBuildFrom
在當前 Scala 集合庫中扮演了最關鍵的角色。而它,也如引言所道,又是其中最具爭議的設計。
本文將詳細介紹 CanBuildFrom
的特性,它為何飽受詬病,而又解決了哪些問題。
map 的定義
以下是馬丁在 Scala -- the Simple Parts talk 中 PPT 的一頁:
我們來看下 map
的型別到底有多醜?
def map[B, That](f: Elem => B)(implicit bf: CanBuildFrom[Repr, B, That]): That複製程式碼
如果你接觸 Scala 不深,以上的 implicit
的確費解。其實它無非是在 Scala 中實現 Type class 的一種方式,這裡你可以簡單理解為:如此 map
就會在隱式範圍中自動尋找關於 bf
物件的實現。
為什麼要採用這種方式呢?我們說過,CanBuildFrom
是極其強大的,以下是它解決的三大問題。
一、集合型別變換
map
最常見的操作,就是對集合中的元素進行轉化,例如:
List(1, 2, 3).map(_.toString) // List[String] = List(1, 2, 3)
"foo".map(c => c.toUpper) // String = FOO
"foo".map(c => c.toInt) // scala.collection.immutable.IndexedSeq[Int] = Vector(102, 111, 111)複製程式碼
這裡,一個 String
可被視為 Char
元素的集合,它如同其它集合一樣,在被 map
之後,有兩種情況:
- 型別不變,還是返回一個
String
- 型別變了,返回一個
scala.collection.immutable.IndexedSeq[Int]
這是 CanBuildFrom
要解決的第一個問題 — 對要 map
的集合進行「型別修改」。
還記得 CanBuildFrom
的定義嗎?
trait CanBuildFrom[-From, -Elem, +To] {
// 建立一個新的構造器(builder)
def apply(from: From): Builder[Elem, To]
}複製程式碼
複習下:「有這麼一個方法,由給定的 From 型別的集合,使用 Elem 型別,建立 To 型別的集合」。
def map[B, That](f: Char => B)(implicit bf: CanBuildFrom[String, B, That]): That複製程式碼
我們把型別引數 Elem
替換成 Char
,From
替換為 String
,如此便實現了 String
型別的 map
操作。
二、不同的集合型別引數
我們知道,Seq[A]
、Set[A]
繼承了 Iterable[A]
,三者有個相同點 — 都有一個型別引數。然而 Map[K, V]
是個特例,它也繼承了 Iterable[A]
,但卻擁有兩個。
這就帶來了一個問題。如果 List[A]
繼承了 Iterable[A]
中的 map
,需要一個返回型別 List[B]
,然而對 HashMap[K, V]
操作,卻期望返回 HashMap[L, W]
,這裡的型別引數數量出現了不一致的情況。
CanBuildFrom
再一次化解了這個問題。
- 當
Repr
是List[_]
時,That
變成了List[B]
- 當
Repr
是HashMap[_, _]
時,B
就是元祖(K, V)
,而That
變成了HashMap[K, V]
。
三、有序集合的排序問題
思考有序集合之間的元素轉換問題,比如 TreeSet
。它們任何一次 map
操作,都需要能夠對元素進行排序,那麼這種元素間的排序關係如何被定義呢?
同樣是定義一個 CanBuildFrom
的隱式物件,它的型別為 CanBuildFrom[TreeSet[_], X, TreeSet[X]]
。其中 X
代表集合元素的型別,於是我們就可以利用相同的技術,定義一個 Ordering[X]
隱式物件,它解釋瞭如何對 TreeSet
的元素進行排序。
Simple is not Easy
上面列舉了 CanBuildFrom
的各種美好,然而它還是逃脫不了被新方案替代的命運。究其原因,也可以用馬丁 talk 的另一頁 PPT 解釋,Simple != Easy 。相信這也是 Scala 在「學院」與「工業」之間做出的平衡選擇。