В Scala 2.8 пробоя
В 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
?
4 ответа:
ответ находится на определение
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]]
. Но есть ли такое определение?trait CanBuildFrom[-From, -Elem, +To] extends AnyRef
так
CanBuildFrom
является противоположным вариантом по параметру первого типа. Потому чтоNothing
является нижним классом (т. е. это подкласс всего), что означает любой класс может быть использован вместоNothing
.поскольку такой конструктор существует, Scala может использовать его для создания желаемый результат.
Про Строителей
многие методы из библиотеки коллекций Scala состоят из взятия исходной коллекции, обработки ее каким-либо образом (в случае
map
, Преобразуя каждый элемент) и сохраняя результаты в новой коллекции.чтобы максимизировать повторное использование кода, это хранение результатов осуществляется с помощью строитель (
scala.collection.mutable.Builder
), который в основном поддерживает две операции: добавление элементов, и возвращая полученную коллекцию. Тип этой результирующей коллекции будет зависеть от типа компоновщика. Таким образом,List
строитель возвратитьList
, aMap
строитель возвратитьMap
и так далее. Реализацияmap
метод не должен заботиться о типе результата: строитель заботится о нем.с другой стороны, это означает, что
map
нужно как-то получить этого строителя. Проблемы, с которыми сталкиваются при проектировании в Scala 2.8 Коллекции - это то, как выбрать лучшего строителя. Например, если бы я написалMap('a' -> 1).map(_.swap)
, я хотел бы получитьMap(1 -> 'a')
обратно. С другой стороны,Map('a' -> 1).map(_._1)
не могу вернуть aMap
(она возвращает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
приходите, давайте рассмотрим определение :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
.Скала подберет для вас наиболее конкретные неявные. В первом случае, это был первый
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]]
.
Я хотел бы опираться на ответ Даниила. Это было очень тщательно, но, как отмечается в комментариях, это не объясняет, что делает прорыв.
принято от Re: поддержка явных Строителей (2009-10-23), вот что я считаю, что прорыв тут:
Он дает компилятору предложение о том, какой Строитель выбрать неявно (по сути, он позволяет компилятору выбрать, какая фабрика, по его мнению, лучше всего подходит для ситуации.)
для пример см. ниже:
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)
ответ Даниила собрала велик, и его следует читать вместе с архитектура коллекций Scala (Глава 25 программирования в Scala).
я просто хотел уточнить, почему он называется
breakOut
:почему это называется
breakOut
?потому что мы хотим выйти из одного типа в другой:
вырваться из какого типа в какой тип? Давайте посмотрим на
простой пример, чтобы понять, что
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]