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 breakOut
jest wywoływany jako argument do mojego List
? 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]]
.
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)
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.
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]
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