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

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, a Map строитель возвратить Map и так далее. Реализация map метод не должен заботиться о типе результата: строитель заботится о нем.

с другой стороны, это означает, что map нужно как-то получить этого строителя. Проблемы, с которыми сталкиваются при проектировании в Scala 2.8 Коллекции - это то, как выбрать лучшего строителя. Например, если бы я написал Map('a' -> 1).map(_.swap), я хотел бы получить Map(1 -> 'a') обратно. С другой стороны,Map('a' -> 1).map(_._1) не могу вернуть a Map (она возвращает 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]