Ответ 1
Ответ найден в определении 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
So CanBuildFrom
является противоречивым по своему первому параметру. Поскольку Nothing
является нижним классом (т.е. Является подклассом всего), это означает, что вместо Nothing
можно использовать любой класс.
Поскольку такой строитель существует, Scala может использовать его для получения желаемого результата.
О конструкторах
Множество методов из библиотеки коллекций Scala состоит из взятия оригинальной коллекции, ее обработки каким-то образом (в случае map
, преобразования каждого элемента) и сохранения результатов в новой коллекции.
Чтобы максимизировать повторное использование кода, это сохранение результатов выполняется через построитель (scala.collection.mutable.Builder
), который в основном поддерживает две операции: добавление элементов и возврат полученной коллекции. Тип этой результирующей коллекции будет зависеть от типа строителя. Таким образом, построитель List
вернет List
, построитель map
вернет map
и так далее. Реализация метода map
не должна касаться типа результата: строитель позаботится об этом.
С другой стороны, это означает, что map
должен каким-то образом получить этот конструктор. Проблема при проектировании Scala 2.8 Коллекции заключалась в том, как выбрать лучший строитель. Например, если бы я написал 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
и 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]]
.