Объяснять, почему это происходит, долго: в Scala 2.9.x (не знаю, как в других версиях) такие методы коллекций, как filter или map, опираются на механизм 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
— это новый тип коллекции, который может отличаться от текущего типа. Классический пример — когда вы сопоставляете, например, BitSet с помощью 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, если бы поведение было другим, это означало бы, что каждая карта непараллельных коллекций также выполнялась бы параллельно.
Дело в том, что для того, как коллекции Scala разработаны в 2.9.x, нет альтернативы. Если компилятор не предоставляет CanBuildFrom
для параллельной коллекции, карта не будет параллельной.
person
Edmondo1984
schedule
18.01.2013