Scala 中的集合(四):CanBuildFrom

ScalaCool發表於2017-08-01

本文由 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 的一頁:


type of map is ugly
type of map is ugly

我們來看下 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 替換成 CharFrom 替換為 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 再一次化解了這個問題。

  • ReprList[_] 時,That 變成了 List[B]
  • ReprHashMap[_, _] 時,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 在「學院」與「工業」之間做出的平衡選擇。


simple is not easy
simple is not easy

參考

相關文章