Scala 2.8 breakOut

W Scali 2.8, istnieje obiekt w 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()
 }

Powiedziano mi, że skutkuje to:

> 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)
Co tu się dzieje? Dlaczego breakOutjest wywoływany jako argument do mojego List?
Author: DNA, 2009-11-11

4 answers

Odpowiedź znajduje się na definicji map:

def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That 

Zauważ, że ma dwa parametry. Pierwsza to twoja funkcja, a druga to ukryta. Jeśli nie podasz tego dorozumianego, Scala wybierze najbardziej konkretny dostępny.

O firmie breakOut

Więc, jaki jest cel breakOut? Rozważmy przykład podany Dla pytania, bierzemy listę łańcuchów, przekształcamy każdy łańcuch w krotkę (Int, String), a następnie produkujemy a Map out of it. Najbardziej oczywistym sposobem na to jest utworzenie pośredniej kolekcji List[(Int, String)], a następnie jej przekształcenie.

Biorąc pod uwagę, że map używa Builder do wytworzenia zbioru wynikowego, czy nie byłoby możliwe pominięcie pośrednika List i zebranie wyników bezpośrednio do Map? Najwyraźniej tak. Aby to zrobić, musimy jednak przekazać odpowiednie CanBuildFrom do map i to właśnie robi breakOut.

Przyjrzyjmy się zatem definicji 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()
  }

Zauważ, że {[18] } jest parametryzowana i zwraca instancję CanBuildFrom. Tak się składa, że rodzaje From, T i To zostały już wywnioskowane, ponieważ wiemy, że map oczekuje CanBuildFrom[List[String], (Int, String), Map[Int, String]]. Dlatego:

From = List[String]
T = (Int, String)
To = Map[Int, String]

Podsumowując, przyjrzyjmy się implicit otrzymanemu przez breakOut. Jest typu CanBuildFrom[Nothing,T,To]. Znamy już wszystkie te typy,więc możemy stwierdzić, że potrzebujemy implicit of type CanBuildFrom[Nothing,(Int,String),Map[Int,String]]. Ale czy jest taki definicja?

Spójrzmy na definicjęCanBuildFrom:

trait CanBuildFrom[-From, -Elem, +To] 
extends AnyRef

Więc {[27] } jest kontra-wariantem pierwszego parametru typu. Ponieważ Nothing jest klasą dolną (tzn. jest podklasą wszystkiego), oznacza to, że każda klasa może być użyta zamiast Nothing.

Ponieważ taki konstruktor istnieje, Scala może go użyć do wytworzenia pożądanego wyjścia.

O Budowniczych

Wiele metod ze zbiorów Biblioteki Scali polega na zbiór oryginalny, przetwarzając go w jakiś sposób (w przypadku map, przekształcając każdy element) i przechowując wyniki w nowym zbiorze.

Aby zmaksymalizować ponowne użycie kodu, zapisywanie wyników odbywa się za pomocą konstruktora (scala.collection.mutable.Builder), który zasadniczo obsługuje dwie operacje: dodawanie elementów i zwracanie wynikowej kolekcji. Typ tej wynikowej kolekcji będzie zależał od typu konstruktora. Tak więc, budowniczy List zwróci List, budowniczy Map zwróci Map, i tak dalej. Implementacja metody map nie musi martwić się o rodzaj wyniku: konstruktor dba o to.

Z drugiej strony, oznacza to, że map musi jakoś otrzymać tego budowniczego. Problemem przy projektowaniu kolekcji Scala 2.8 było wybranie najlepszego możliwego konstruktora. Na przykład, gdybym miał napisać Map('a' -> 1).map(_.swap), chciałbym dostać Map(1 -> 'a') z powrotem. Z drugiej strony, a Map('a' -> 1).map(_._1) nie może zwrócić a Map (zwraca Iterable).

Magia tworzenia najlepszego możliwego Builder ze znanych typów wyrażenia jest wykonywana za pomocą tego CanBuildFrom implicit.

O firmie CanBuildFrom

Aby lepiej wyjaśnić, o co chodzi, podam przykład, w którym odwzorowywana kolekcja to Map zamiast List. Wrócę do List później. Na razie rozważmy te dwa wyrażenia:

Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)

Pierwszy zwraca Map, a drugi zwraca Iterable. Magia o zwróceniu odpowiedniej kolekcji jest dziełem CanBuildFrom. Rozważmy definicję map ponownie, aby ją zrozumieć.

Metoda map jest dziedziczona z TraversableLike. Jest ona parametryzowana na B i That i wykorzystuje parametry typu A i Repr, które parametryzują klasę. Spójrzmy na obie definicje razem:

Klasa TraversableLike jest zdefiniowana jako:

trait TraversableLike[+A, +Repr] 
extends HasNewBuilder[A, Repr] with AnyRef

def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That 

Aby zrozumieć skąd pochodzą A i Repr, rozważmy definicję Map:

trait Map[A, +B] 
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]

Ponieważ TraversableLike jest dziedziczona przez wszystkie cechy, które rozciągają Map, A i Repr mogą być dziedziczone po każdym z nich. Ostatni dostaje preferencje. Tak więc, zgodnie z definicją niezmiennego Map i wszystkimi cechami, które łączą go z TraversableLike, mamy:

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

Jeśli przekażesz parametry typu Map[Int, String] w całym łańcuchu, okaże się, że typy przekazane do TraversableLike, a zatem używane przez map, to:

A = (Int,String)
Repr = Map[Int, String]

Going wracając do przykładu, pierwsza mapa odbiera funkcję typu ((Int, String)) => (Int, Int), a druga mapa odbiera funkcję typu ((Int, String)) => String. Używam podwójnego nawiasu, aby podkreślić, że otrzymujemy krotkę, ponieważ taki jest typ A, jak widzieliśmy.

Z tą informacją, rozważmy inne typy.

map Function.tupled(_ -> _.length):
B = (Int, Int)

map (_._2):
B = String

Widzimy, że typ zwracany przez pierwszy map to Map[Int,Int], a drugi to Iterable[String]. Patrząc na definicję map, łatwo zauważyć, że są to wartości That. Ale skąd pochodzą?

Jeśli spojrzymy wewnątrz obiektów towarzyszących zaangażowanych klas, zobaczymy pewne niejawne deklaracje je dostarczające. W obiekcie Map:

implicit def  canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]  

I na obiekcie Iterable, którego klasa jest rozszerzona o Map:

implicit def  canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]  

Te definicje dostarczają fabryk parametryzowanych CanBuildFrom.

Scala wybierze najbardziej konkretny Ukryty dostępny. W pierwszym przypadku był to pierwszy CanBuildFrom. W drugim przypadku, jako pierwszy nie pasował, wybrał drugi CanBuildFrom.

Wróć do pytania

Zobaczmy kod dla pytania, List i map's definicja (ponownie), aby zobaczyć, jak typy są wnioskowane:

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 

Typem List("London", "Paris") jest List[String], więc typy A i Repr zdefiniowane na TraversableLike to:

A = String
Repr = List[String]

Typ dla (x => (x.length, x)) to (String) => (Int, String), więc Typ B to:

B = (Int, String)

Ostatni Nieznany typ, That jest typem wynik map, a my już to mamy:

val map : Map[Int,String] =

Więc,

That = Map[Int, String]

Oznacza to, że breakOut musi koniecznie zwrócić typ lub Podtyp CanBuildFrom[List[String], (Int, String), Map[Int, String]].

 317
Author: Daniel C. Sobral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-27 15:36:25

Chciałbym oprzeć się na odpowiedzi Daniela. To było bardzo dokładne, ale jak wspomniano w komentarzach, to nie wyjaśnia, co breakout robi.

Wzięte z Re: wsparcie dla budowniczych (2009-10-23), oto co moim zdaniem robi breakout:

Daje kompilatorowi sugestię, który Konstruktor wybrać domyślnie (zasadniczo pozwala kompilatorowi wybrać, która fabryka jego zdaniem najlepiej pasuje do sytuacji.)

Na przykład, zobacz "po": {]}

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)

Możesz zobaczyć, że typ zwracany jest domyślnie wybrany przez kompilator, aby najlepiej pasował do oczekiwanego typu. W zależności od tego, jak zadeklarujesz zmienną otrzymującą, otrzymasz różne wyniki.

Poniższy sposób byłby równoważnym sposobem określenia konstruktora. Uwaga w tym przypadku kompilator wywnioskuje oczekiwany typ na podstawie typu konstruktora:

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)
 85
Author: Austen Holmes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-06 17:34:03

Odpowiedź Daniela Sobrala jest świetna i powinna być czytana razem z architekturą zbiorów Scali (Rozdział 25 programowania w Scali).

Chciałem tylko rozwinąć, dlaczego nazywa się breakOut:

Dlaczego nazywa się breakOut?

Ponieważ chcemy wyłamać się z jednego typu i na inny:

Wyłamać się z jakiego typu na jaki typ? Spójrzmy na funkcję map na Seq jako przykład:

Seq.map[B, That](f: (A) -> B)(implicit bf: CanBuildFrom[Seq[A], B, That]): That

Jeśli w 2007 roku, po raz pierwszy w historii, udało się stworzyć mapę bezpośrednio z odwzorowania na elementy sekwencji, takie jak:

val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))

Kompilator by narzekał:

error: type mismatch;
found   : Seq[(String, Int)]
required: Map[String,Int]

Powodem jest to, że Seq wie tylko jak zbudować kolejny Seq (tzn. istnieje ukryta fabryka konstruktorów CanBuildFrom[Seq[_], B, Seq[B]] dostępna, ale nie ma nie ma fabryka konstruktorów od Seq do Map).

Aby skompilować, musimy jakoś breakOut wymaganego typu, i być w stanie skonstruować budowniczego, który wytwarza Mapa dla funkcji map do użycia.

Jak wyjaśnił Daniel, breakOut ma następujący podpis:

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 jest podklasą wszystkich klas, więc każda fabryka konstruktorów może być podstawiona w miejsce implicit b: CanBuildFrom[Nothing, T, To]. Jeśli użyliśmy funkcji breakOut, aby podać parametr implicit:

val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))(collection.breakOut)
Kompilator może być skompilowany, ponieważ breakOut jest w stanie dostarczyć wymagany typ CanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]], podczas gdy kompilator jest w stanie znaleźć ukrytą fabrykę typu CanBuildFrom[Map[_, _], (A, B), Map[A, B]], w miejsce CanBuildFrom[Nothing, T, To], dla breakOut, aby użyć do stworzenia rzeczywistego budowniczego.

Zauważ, że CanBuildFrom[Map[_, _], (A, B), Map[A, B]] jest zdefiniowany w Map i po prostu inicjuje MapBuilder, który używa mapy bazowej.

Mam nadzieję, że to wszystko wyjaśni.
 6
Author: Dzhu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-30 14:26:24

Prosty przykład, aby zrozumieć, co breakOut robi:

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]
 3
Author: man,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-29 22:00:30