對它發生的原因的解釋是一個很長的故事:在Scala 2.9.x(我不知道其他版本)這些收集方法(如過濾器或映射)依賴於CanBuildFrom
機制。這個想法是,你有被用來創建一個建設者新集合的隱含參數:
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
b.sizeHint(this)
for (x <- this) b += f(x)
b.result
}
由於這種機制,地圖方法在TraversableLike
特質只定義和它的子類不需要重寫它。如您所見,在方法映射簽名中有許多類型參數。讓我們來看看在瑣碎的:
- 的
B
這是新的集合
- 的
A
的元素的類型是源集合中的元素的類型
讓我們來看看在更復雜的問題:
That
是新類型的集合,可以是從真正水流不同nt類型。一個經典的例子是,當你使用一個toString映射例如位集合:
scala> val a = BitSet(1,3,5)
a scala.collection.immutable.BitSet = BitSet(1, 3, 5)
scala> a.map {_.toString}
res2: scala.collection.immutable.Set[java.lang.String] = Set(1, 3, 5)
因爲它是非法的創建BitSet[String]
地圖結果將是一個Set[String]
最後Repr
在當前採集的類型。當您嘗試通過函數映射集合時,編譯器將使用類型參數解析合適的CanBuildFrom。
因爲它是合理的,映射方法被覆蓋在並行收集在ParIterableLike
如下所示:
def map[S, That](f: T => S)(implicit bf: CanBuildFrom[Repr, S, That]): That = bf ifParallel { pbf =>
executeAndWaitResult(new Map[S, That](f, pbf, splitter) mapResult { _.result })
} otherwise seq.map(f)(bf2seq(bf))
正如你可以看到,該方法具有相同的簽名,但它採用了不同的方法:它測試提供的CanBuildFrom
是否是並行的,否則回落到默認實現。 因此,Scala並行集合使用特殊的CanBuildFrom(並行)爲地圖方法創建並行構建器。
但是,當你做
(if(runParallel) theList.par else theList) map println //Doesn't run in parallel
發生什麼地圖方法大幹快上的
(if(runParallel) theList.par else theList)
返回類型爲兩個類的第一個共同祖先的結果執行(在這種情況下,只有一定數量的特性混合在一起)。既然它是一個共同的祖先,那麼類型參數Repr
將會是某種共同祖先的兩種集合表示,我們稱之爲Repr1
。
結論
當你調用map
方法,編譯器應該找到一個操作的適合CanBuildFrom[Repr, B, That]
。由於我們的Repr1
不是平行集合中的一個,因此不會有任何CanBuildFrom[Repr1,B,That]
能夠提供並行構建器。對於Scala集合的實現,這實際上是一個正確的行爲,如果行爲不同,那意味着並行集合的每個映射都將並行運行。
這裏的要點是,對於如何在2.9.x中設計Scala集合,沒有其他選擇。如果編譯器沒有爲並行集合提供CanBuildFrom
,則映射將不會平行。
感謝您的詳細解答。 – omid