В 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]