В 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
?
Ответ найден в определении 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]]
.
Traversable
и хотел превратить его в карту. Я ценю время, которое вы уделили этому ответу - правда - но вы не против его изменить? Я изо всех сил пытаюсь понять, что происходит, потому что это не точно отвечает на вопрос, который я задал
breakOut
на вопрос, когда вернусь к breakOut
. Я думаю, что важно начать с примера на Map
, потому что он проясняет, какова цель CanBuildFrom
. Я постараюсь прояснить переход к вашему вопросу.
Я хотел бы основываться на ответе Даниэля. Он был очень тщательным, но, как отмечается в комментариях, он не объясняет, что происходит.
Взято из Re: Поддержка явных сборщиков (2009-10-23), вот что я считаю, что прорыв:
Он дает компилятору предложение относительно того, какой Builder выбрать неявно (по сути, он позволяет компилятору выбрать, какой factory он считает лучше всего подходит).
Например, см. следующее:
scala> import scala.collection.generic._
import scala.collection.generic._
scala> import scala.collection._
import scala.collection._
scala> import scala.collection.mutable._
import scala.collection.mutable._
scala>
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()
| }
breakOut: [From, T, To]
| (implicit b: scala.collection.generic.CanBuildFrom[Nothing,T,To])
| java.lang.Object with
| scala.collection.generic.CanBuildFrom[From,T,To]
scala> val l = List(1, 2, 3)
l: List[Int] = List(1, 2, 3)
scala> val imp = l.map(_ + 1)(breakOut)
imp: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 4)
scala> val arr: Array[Int] = l.map(_ + 1)(breakOut)
imp: Array[Int] = Array(2, 3, 4)
scala> val stream: Stream[Int] = l.map(_ + 1)(breakOut)
stream: Stream[Int] = Stream(2, ?)
scala> val seq: Seq[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Seq[Int] = ArrayBuffer(2, 3, 4)
scala> val set: Set[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Set[Int] = Set(2, 4, 3)
scala> val hashSet: HashSet[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.HashSet[Int] = Set(2, 4, 3)
Вы можете видеть, что тип возврата неявно выбирается компилятором, чтобы наилучшим образом соответствовать ожидаемому типу. В зависимости от того, как вы объявляете принимающую переменную, вы получаете разные результаты.
Следующим будет эквивалентный способ указания строителя. Обратите внимание, что в этом случае компилятор выведет ожидаемый тип на основе типа строителя:
scala> def buildWith[From, T, To](b : Builder[T, To]) =
| new CanBuildFrom[From, T, To] {
| def apply(from: From) = b ; def apply() = b
| }
buildWith: [From, T, To]
| (b: scala.collection.mutable.Builder[T,To])
| java.lang.Object with
| scala.collection.generic.CanBuildFrom[From,T,To]
scala> val a = l.map(_ + 1)(buildWith(Array.newBuilder[Int]))
a: Array[Int] = Array(2, 3, 4)
breakOut
"? Я думаю, что что-то вроде convert
или buildADifferentTypeOfCollection
(но короче) могло бы быть легче запомнить.
Даниэль Собрал ответ велик, и его следует читать вместе с Архитектура Scala Коллекций (глава 25 программирования в Scala).
Я просто хотел уточнить, почему он называется breakOut
:
breakOut
?Поскольку мы хотим, чтобы вырвался из одного типа и в другой:
Разрыв какого типа в какой тип? Давайте рассмотрим функцию map
на Seq
в качестве примера:
Seq.map[B, That](f: (A) -> B)(implicit bf: CanBuildFrom[Seq[A], B, That]): That
Если мы хотим построить карту непосредственно из сопоставления по элементам последовательности, например:
val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))
Компилятор будет жаловаться:
error: type mismatch;
found : Seq[(String, Int)]
required: Map[String,Int]
Причина заключается в том, что Seq знает, как построить еще один Seq (т.е. существует неявный CanBuildFrom[Seq[_], B, Seq[B]]
builder factory, но существует конструктор NO factory от Seq to Map),
Для компиляции нам нужно как-то breakOut
требования типа, и иметь возможность построить построитель, который создает карту для функции map
, которая будет использоваться.
Как пояснил Даниил, breakOut имеет следующую подпись:
def breakOut[From, T, To](implicit b: CanBuildFrom[Nothing, T, To]): CanBuildFrom[From, T, To] =
// can't just return b because the argument to apply could be cast to From in b
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply()
def apply() = b.apply()
}
Nothing
является подклассом всех классов, поэтому любой строитель factory может быть заменен вместо implicit b: CanBuildFrom[Nothing, T, To]
. Если мы использовали функцию breakOut для предоставления неявного параметра:
val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))(collection.breakOut)
Он будет компилироваться, потому что breakOut
может предоставить требуемый тип CanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
, в то время как компилятор может найти неявный строитель factory типа CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
вместо CanBuildFrom[Nothing, T, To]
, для breakOut для создания фактического строителя.
Обратите внимание, что CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
определяется в Map и просто инициирует MapBuilder
, который использует базовую карту.
Надеюсь, что это прояснит ситуацию.
Простой пример, чтобы понять, что делает breakOut
:
scala> import collection.breakOut
import collection.breakOut
scala> val set = Set(1, 2, 3, 4)
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)
scala> set.map(_ % 2)
res0: scala.collection.immutable.Set[Int] = Set(1, 0)
scala> val seq:Seq[Int] = set.map(_ % 2)(breakOut)
seq: Seq[Int] = Vector(1, 0, 1, 0) // map created a Seq[Int] instead of the default Set[Int]
val seq:Seq[Int] = set.map(_ % 2).toVector
не даст вам повторных значений, так как Set
был сохранен для map
.
set.map(_ % 2)
сначала создает Set(1, 0)
, который затем преобразуется в Vector(1, 0)
.
List
, а дляmap
.