为什么会发生这种情况的解释说来话长:在 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
}
由于这种机制,map 方法仅在TraversableLike
trait 中定义,其子类不需要覆盖它。如您所见,在方法映射签名内部有许多类型参数。让我们看看琐碎的:
让我们看看更复杂的:
That
是新的集合类型,可以与当前类型不同。一个经典的例子是当你使用 toString 映射 BitSet 时:
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。
由于它是合理的,map 方法已在并行集合中被覆盖,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(并行集合),它为 map 方法创建并行构建器。
但是,当你这样做时会发生什么
(if(runParallel) theList.par else theList) map println //Doesn't run in parallel
map 方法是在结果上执行的
(if(runParallel) theList.par else theList)
其返回类型是两个类的第一个共同祖先(在这种情况下,只是一定数量的特征混合在一起)。既然是共同祖先,就是类型参数Repr
会是两种集合的某种共同祖先的表示,姑且称之为Repr1
。
结论
当您调用该map
方法时,编译器应该找到一个适合CanBuildFrom[Repr, B, That]
该操作的方法。由于我们Repr1
不是并行集合中的一个,因此不会有任何CanBuildFrom[Repr1,B,That]
能够提供并行构建器的能力。这实际上是关于 Scala 集合实现的正确行为,如果行为不同,则意味着非并行集合的每个映射也将并行运行。
这里的重点是,对于 Scala 集合在 2.9.x 中的设计方式,没有其他选择。如果编译器没有CanBuildFrom
为并行集合提供 a,则映射将不是并行的。