В Scala 2.8 есть объект в scala.collection.package.scala
:
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()
}
Мне сказали, что это приводит к:
> import scala.collection.breakOut
> val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)
map: Map[Int,String] = Map(6 -> London, 5 -> Paris)
Что здесь происходит? Почему меня breakOut
называют аргументом для моего List
?
scala
scala-2.8
scala-collections
oxbow_lakes
источник
источник
List
, аmap
.Ответы:
Ответ находится на определение
map
:Обратите внимание, что у него есть два параметра. Первая - это ваша функция, а вторая - неявная. Если вы не укажете это неявно, Scala выберет наиболее конкретный из доступных.
Около
breakOut
Итак, какова цель
breakOut
? Рассмотрим пример, приведенный для вопроса. Вы берете список строк, преобразуете каждую строку в кортеж(Int, String)
и затем создаетеMap
из него. Наиболее очевидный способ сделать это - создать промежуточнуюList[(Int, String)]
коллекцию, а затем преобразовать ее.Учитывая, что для создания результирующей коллекции
map
используется aBuilder
, нельзя ли было бы пропустить посредникаList
и собрать результаты непосредственно вMap
? Очевидно, да, это так. Для этого, однако, мы должны пройти надлежащее ,CanBuildFrom
чтобыmap
, и это именно то , чтоbreakOut
делает.Давайте посмотрим на определение
breakOut
:Обратите внимание, что
breakOut
он параметризован и возвращает экземплярCanBuildFrom
. Как это бывает, типыFrom
,T
иTo
уже были выведены, потому что мы знаем, чтоmap
ожидаетCanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Следовательно:В заключение давайте рассмотрим неявное, полученное
breakOut
само по себе. Это типаCanBuildFrom[Nothing,T,To]
. Мы уже знаем все эти типы, поэтому мы можем определить, что нам нужен неявный типCanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Но есть ли такое определение?Давайте посмотрим на
CanBuildFrom
определение:Так что
CanBuildFrom
это противоречивый вариант по параметру первого типа. ПосколькуNothing
это нижний класс (т.е. это подкласс всего), это означает, что любой класс может использоваться вместоNothing
.Поскольку такой конструктор существует, Scala может использовать его для получения желаемого результата.
О строителях
Многие методы из библиотеки коллекций Scala состоят из извлечения исходной коллекции, ее обработки каким-либо образом (в случае
map
преобразования каждого элемента) и сохранения результатов в новой коллекции.Чтобы максимизировать повторное использование кода, это сохранение результатов осуществляется с помощью builder (
scala.collection.mutable.Builder
), который в основном поддерживает две операции: добавление элементов и возврат результирующей коллекции. Тип этой результирующей коллекции будет зависеть от типа компоновщика. Таким образом,List
строитель вернет aList
,Map
строитель вернет aMap
и так далее. Реализация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
позже. А пока рассмотрим эти два выражения:Первое возвращает a,
Map
а второе возвращаетIterable
. Волшебство возвращения подходящей коллекции - работаCanBuildFrom
. Давайте рассмотрим определениеmap
снова, чтобы понять это.Метод
map
унаследован отTraversableLike
. Он параметризован дляB
иThat
, и использует параметры типаA
иRepr
, которые параметризуют класс. Давайте посмотрим оба определения вместе:Класс
TraversableLike
определяется как:Чтобы понять, откуда
A
иRepr
откуда, давайте рассмотрим определение самогоMap
себя:Потому что
TraversableLike
наследуется всеми чертами , которые простираютсяMap
,A
иRepr
может быть унаследован от любого из них. Последний получает предпочтение, хотя. Итак, следуя определению неизменяемогоMap
и всем признакам, которые его связываютTraversableLike
, мы имеем:Если вы передадите параметры типа по
Map[Int, String]
всей цепочке, мы обнаружим, что типы, переданныеTraversableLike
и, следовательно, используемыеmap
:Возвращаясь к примеру, первая карта получает функцию типа,
((Int, String)) => (Int, Int)
а вторая карта получает функцию типа((Int, String)) => String
. Я использую двойные скобки, чтобы подчеркнуть, что получен кортеж,A
как мы уже видели.С этой информацией, давайте рассмотрим другие типы.
Мы можем видеть, что тип, возвращаемый первым
map
- этоMap[Int,Int]
, а второй -Iterable[String]
. Глядя наmap
определение, легко увидеть, что это значенияThat
. Но откуда они берутся?Если мы посмотрим внутрь сопутствующих объектов участвующих классов, мы увидим некоторые неявные объявления, предоставляющие их. На объекте
Map
:И на объекте
Iterable
, класс которого расширенMap
:Эти определения предоставляют фабрики для параметризации
CanBuildFrom
.Scala выберет наиболее конкретную неявную версию из доступных. В первом случае это был первый
CanBuildFrom
. Во втором случае, поскольку первый не совпал, он выбрал второйCanBuildFrom
.Вернуться к вопросу
Давайте посмотрим на код вопроса, определения
List
s иmap
(снова), чтобы увидеть, как типы выводятся:Тип
List("London", "Paris")
isList[String]
, поэтому типыA
иRepr
определены наTraversableLike
:Тип для
(x => (x.length, x))
is(String) => (Int, String)
, поэтому типB
is:Последний неизвестный тип
That
- это тип результатаmap
, и у нас уже есть это:Так,
Это означает
breakOut
, что обязательно должен возвращать тип или подтипCanBuildFrom[List[String], (Int, String), Map[Int, String]]
.источник
Я хотел бы опираться на ответ Дэниела. Это было очень тщательно, но, как отмечено в комментариях, это не объясняет, что делает прорыв.
Взятый из Re: Поддержка явных Строителей (2009-10-23), вот что я считаю прорывом:
Он дает компилятору предложение о том, какой Builder выбирать неявно (по сути, он позволяет компилятору выбирать, какую фабрику он считает наиболее подходящей для ситуации).
Например, смотрите следующее:
Вы можете видеть, что возвращаемый тип неявно выбирается компилятором, чтобы наилучшим образом соответствовать ожидаемому типу. В зависимости от того, как вы объявите получающую переменную, вы получите разные результаты.
Следующее будет эквивалентным способом указать конструктор. Обратите внимание, что в этом случае компилятор выведет ожидаемый тип в зависимости от типа компоновщика:
источник
breakOut
"? Я думаю, что-то вродеconvert
илиbuildADifferentTypeOfCollection
(но короче), возможно, было легче запомнить.Ответ Даниэля Собрала велик, и его следует читать вместе с « Архитектурой коллекций Scala» (глава 25 «Программирование в Scala»).
Я просто хотел уточнить, почему это называется
breakOut
:Почему это называется
breakOut
?Потому что мы хотим вырваться из одного типа в другой :
Вырваться из какого типа в какой тип? Давайте посмотрим на
map
функциюSeq
в качестве примера:Если мы хотим построить карту непосредственно из отображения элементов последовательности, таких как:
Компилятор будет жаловаться:
Причина в том, что Seq знает только, как построить другой Seq (то есть существует неявная
CanBuildFrom[Seq[_], B, Seq[B]]
фабрика компоновщиков, но нет фабрики компоновщиков от Seq до Map).Для того, чтобы скомпилировать, нам нужно как-то
breakOut
соответствовать требованию к типу и быть в состоянии построить конструктор, который создает Map для использованияmap
функции.Как объяснил Даниэль, breakOut имеет следующую подпись:
Nothing
является подклассом всех классов, поэтому любая фабрика компоновщика может быть заменена наimplicit b: CanBuildFrom[Nothing, T, To]
. Если мы использовали функцию breakOut для предоставления неявного параметра:Он будет компилироваться, потому что
breakOut
способен предоставить требуемый типCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
, в то время как компилятор может найти неявную фабрику компоновщика типаCanBuildFrom[Map[_, _], (A, B), Map[A, B]]
,CanBuildFrom[Nothing, T, To]
которую будет использовать breakOut для создания фактического компоновщика.Обратите внимание, что
CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
он определен в Map и просто инициирует объект,MapBuilder
который использует базовую карту.Надеюсь, это прояснит ситуацию.
источник
Простой пример, чтобы понять, что
breakOut
делает:источник
val seq:Seq[Int] = set.map(_ % 2).toVector
не даст вам повторных значений, какSet
было сохранено дляmap
.set.map(_ % 2)
создаетSet(1, 0)
первый, который затем преобразуется вVector(1, 0)
.