Ответ находится на определение map
:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Обратите внимание, что у него есть два параметра. Первая - это ваша функция, а вторая - неявная. Если вы не укажете это неявно, Scala выберет наиболее конкретный из доступных.
Около breakOut
Итак, какова цель breakOut
? Рассмотрим пример, приведенный для вопроса. Вы берете список строк, преобразуете каждую строку в кортеж (Int, String)
и затем создаетеMap
из него. Наиболее очевидный способ сделать это - создать промежуточную List[(Int, String)]
коллекцию, а затем преобразовать ее.
Учитывая, что для создания результирующей коллекции map
используется a Builder
, нельзя ли было бы пропустить посредника List
и собрать результаты непосредственно в Map
? Очевидно, да, это так. Для этого, однако, мы должны пройти надлежащее , CanBuildFrom
чтобы map
, и это именно то , что breakOut
делает.
Давайте посмотрим на определение breakOut
:
def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply() ; def apply() = b.apply()
}
Обратите внимание, что breakOut
он параметризован и возвращает экземпляр CanBuildFrom
. Как это бывает, типы From
, T
и To
уже были выведены, потому что мы знаем, что map
ожидает CanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Следовательно:
From = List[String]
T = (Int, String)
To = Map[Int, String]
В заключение давайте рассмотрим неявное, полученное breakOut
само по себе. Это типаCanBuildFrom[Nothing,T,To]
. Мы уже знаем все эти типы, поэтому мы можем определить, что нам нужен неявный тип CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Но есть ли такое определение?
Давайте посмотрим на CanBuildFrom
определение:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Так что CanBuildFrom
это противоречивый вариант по параметру первого типа. Поскольку Nothing
это нижний класс (т.е. это подкласс всего), это означает, что любой класс может использоваться вместо Nothing
.
Поскольку такой конструктор существует, Scala может использовать его для получения желаемого результата.
О строителях
Многие методы из библиотеки коллекций Scala состоят из извлечения исходной коллекции, ее обработки каким-либо образом (в случае map
преобразования каждого элемента) и сохранения результатов в новой коллекции.
Чтобы максимизировать повторное использование кода, это сохранение результатов осуществляется с помощью builder ( scala.collection.mutable.Builder
), который в основном поддерживает две операции: добавление элементов и возврат результирующей коллекции. Тип этой результирующей коллекции будет зависеть от типа компоновщика. Таким образом, List
строитель вернет a List
, Map
строитель вернет a Map
и так далее. Реализацияmap
метода не должна зависеть от типа результата: об этом заботится строитель.
С другой стороны, это означает, что map
этого строителя нужно как-то получить. Проблема, с которой столкнулись при разработке Scala 2.8 Collections, заключалась в том, как выбрать лучшего строителя из возможных. Например, если бы я написал Map('a' -> 1).map(_.swap)
, я хотел бы получить Map(1 -> 'a')
обратно. С другой стороны, Map('a' -> 1).map(_._1)
не может вернуть Map
(он возвращает Iterable
).
Магия создания наилучшего Builder
из известных типов выражений осуществляется через это CanBuildFrom
неявное.
Около CanBuildFrom
Чтобы лучше объяснить, что происходит, я приведу пример, где отображаемая коллекция - это Map
вместо а List
. Я вернусь к List
позже. А пока рассмотрим эти два выражения:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
Первое возвращает a, Map
а второе возвращает Iterable
. Волшебство возвращения подходящей коллекции - работа CanBuildFrom
. Давайте рассмотрим определение map
снова, чтобы понять это.
Метод map
унаследован от TraversableLike
. Он параметризован для B
и That
, и использует параметры типа A
и Repr
, которые параметризуют класс. Давайте посмотрим оба определения вместе:
Класс TraversableLike
определяется как:
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Чтобы понять, откуда A
и Repr
откуда, давайте рассмотрим определение самого Map
себя:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Потому что TraversableLike
наследуется всеми чертами , которые простираются Map
, A
и Repr
может быть унаследован от любого из них. Последний получает предпочтение, хотя. Итак, следуя определению неизменяемого Map
и всем признакам, которые его связывают TraversableLike
, мы имеем:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]]
extends MapLike[A, B, This]
trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]]
extends PartialFunction[A, B] with IterableLike[(A, B), This] with Subtractable[A, This]
trait IterableLike[+A, +Repr]
extends Equals with TraversableLike[A, Repr]
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
Если вы передадите параметры типа по Map[Int, String]
всей цепочке, мы обнаружим, что типы, переданные TraversableLike
и, следовательно, используемые map
:
A = (Int,String)
Repr = Map[Int, String]
Возвращаясь к примеру, первая карта получает функцию типа, ((Int, String)) => (Int, Int)
а вторая карта получает функцию типа ((Int, String)) => String
. Я использую двойные скобки, чтобы подчеркнуть, что получен кортеж, A
как мы уже видели.
С этой информацией, давайте рассмотрим другие типы.
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
Мы можем видеть, что тип, возвращаемый первым map
- это Map[Int,Int]
, а второй - Iterable[String]
. Глядя на map
определение, легко увидеть, что это значения That
. Но откуда они берутся?
Если мы посмотрим внутрь сопутствующих объектов участвующих классов, мы увидим некоторые неявные объявления, предоставляющие их. На объекте Map
:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
И на объекте Iterable
, класс которого расширен Map
:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
Эти определения предоставляют фабрики для параметризации CanBuildFrom
.
Scala выберет наиболее конкретную неявную версию из доступных. В первом случае это был первый CanBuildFrom
. Во втором случае, поскольку первый не совпал, он выбрал второй CanBuildFrom
.
Вернуться к вопросу
Давайте посмотрим на код вопроса, определения List
s и map
(снова), чтобы увидеть, как типы выводятся:
val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)
sealed abstract class List[+A]
extends LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqLike[A, List[A]]
trait LinearSeqLike[+A, +Repr <: LinearSeqLike[A, Repr]]
extends SeqLike[A, Repr]
trait SeqLike[+A, +Repr]
extends IterableLike[A, Repr]
trait IterableLike[+A, +Repr]
extends Equals with TraversableLike[A, Repr]
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Тип List("London", "Paris")
is List[String]
, поэтому типы A
и Repr
определены на TraversableLike
:
A = String
Repr = List[String]
Тип для (x => (x.length, x))
is (String) => (Int, String)
, поэтому тип B
is:
B = (Int, String)
Последний неизвестный тип That
- это тип результата map
, и у нас уже есть это:
val map : Map[Int,String] =
Так,
That = Map[Int, String]
Это означает breakOut
, что обязательно должен возвращать тип или подтип CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.
List
, аmap
.