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