Scala 2.8 breakOut - PullRequest
       40

Scala 2.8 breakOut

221 голосов
/ 11 ноября 2009

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

321 голосов
/ 11 ноября 2009

Ответ найден по определению 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

Таким образом, CanBuildFrom является противоположным по своему первому параметру типа. Поскольку Nothing является нижним классом (т.е. это подкласс всего), это означает, что любой класс может использоваться вместо Nothing.

Поскольку такой конструктор существует, Scala может использовать его для получения желаемого результата.

О строителях

Множество методов из библиотеки коллекций Scala состоит в том, чтобы взять исходную коллекцию, как-то ее обработать (в случае map, преобразовать каждый элемент) и сохранить результаты в новой коллекции.

Чтобы максимизировать повторное использование кода, это сохранение результатов осуществляется с помощью builder (scala.collection.mutable.Builder), который в основном поддерживает две операции: добавление элементов и возврат полученной коллекции. Тип этой результирующей коллекции будет зависеть от типа компоновщика. Таким образом, List строитель вернет List, Map строитель вернет Map и так далее. Реализация метода map не должна зависеть от типа результата: об этом заботится строитель.

С другой стороны, это означает, что map нужно каким-то образом получить этого строителя. Проблема, с которой столкнулись при разработке Scala 2.8 Collections, заключалась в том, как выбрать лучшего строителя из возможных. Например, если бы я написал 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, давайте рассмотрим определение самого 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]].

86 голосов
/ 13 сентября 2011

Я бы хотел опираться на ответ Даниэля. Это было очень тщательно, но, как отмечено в комментариях, это не объясняет, что делает прорыв.

Взято из Re: Поддержка явных Строителей (2009-10-23), вот что я думаю, прорыв делает:

Он дает компилятору предложение о том, какой Builder выбирать неявно (по сути, он позволяет компилятору выбирать, какую фабрику он считает наиболее подходящей для ситуации.)

Например, см. Следующее:

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)
6 голосов
/ 30 апреля 2017

Ответ Даниэля Собрала велик, и его следует читать вместе с Архитектура коллекций 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]], но есть фабрика NO от Seq до Map).

Чтобы скомпилировать, нам нужно как-то breakOut требования к типу , и иметь возможность построить конструктор, который создает Map для функции 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 является подклассом всех классов, поэтому любая фабрика строителей может быть заменена вместо 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]], в то время как компилятор может найти фабрику неявных компоновщиков типа CanBuildFrom[Map[_, _], (A, B), Map[A, B]] вместо CanBuildFrom[Nothing, T, To], для breakOut для использовать для создания фактического строителя.

Обратите внимание, что CanBuildFrom[Map[_, _], (A, B), Map[A, B]] определено в Map и просто инициирует MapBuilder, который использует базовую карту.

Надеюсь, это прояснит ситуацию.

3 голосов
/ 18 октября 2016

Простой пример, чтобы понять, что делает 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]
...