В 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
to 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]
в заключение рассмотрим неявное, полученное . Это тип CanBuildFrom[Nothing,T,To]
. Мы уже знаем все эти типы, поэтому мы можем определить, что нам нужен неявный тип CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Но существует ли такое определение?
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
так CanBuildFrom
является contra-variant для параметра первого типа. Потому что Nothing
является нижним классом (т. е. это подкласс всего), что означает любой класс может использоваться вместо Nothing
.
поскольку такой строитель существует, Scala может использовать его для создания желаемый результат.
Про Строителей
многие методы из библиотеки коллекций Scala состоят в том, чтобы взять исходную коллекцию, обработать ее каким-то образом (в случае map
, Преобразуя каждый элемент) и сохраняя результаты в новой коллекции.
чтобы максимизировать повторное использование кода, это хранение результатов осуществляется с помощью строитель (scala.collection.mutable.Builder
), который в основном поддерживает две операции: добавление элементов, и возвращаем полученную коллекцию. Тип этой результирующей коллекции будет зависеть от типа построителя. Таким образом,List
builder вернет List
, a Map
builder вернет 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)
первый возвращает 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
.
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")
is List[String]
, чтобы A
и Repr
определен TraversableLike
являются:
A = String
Repr = List[String]
тип (x => (x.length, x))
is (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]]
.
Я хотел бы основываться на ответе Даниила. Это было очень тщательно, но, как отмечается в комментариях, это не объясняет, что делает breakout.
принято от Re: поддержка явных Строителей (2009-10-23), вот что я считаю breakout делает:
Он дает компилятору предложение о том, какой Строитель выбрать неявно (по сути, он позволяет компилятору выбрать, какая фабрика, по его мнению, лучше всего подходит для ситуации.)
для например, видим следующее:
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]