Ответ найден по определению 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
использует 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
строитель вернет List
, Map
строитель вернет 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)
Первый возвращает 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
и 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")
равен List[String]
, поэтому типы A
и Repr
, определенные для TraversableLike
:
A = String
Repr = List[String]
Тип для (x => (x.length, x))
- (String) => (Int, String)
, поэтому тип B
:
B = (Int, String)
Последний неизвестный тип, That
- это тип результата map
, и он у нас уже есть:
val map : Map[Int,String] =
Итак,
That = Map[Int, String]
Это означает, что breakOut
обязательно должен возвращать тип или подтип CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.