In Scala 2.8 gibt es ein Objekt in 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()
}
Mir wurde gesagt, dass dies zu folgenden Ergebnissen führt:
> 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)
Was geht hier vor sich? Warum wird breakOut
ich als Argument für mich gerufen List
?
scala
scala-2.8
scala-collections
oxbow_lakes
quelle
quelle
List
, sondern fürmap
.Antworten:
Die Antwort findet sich in der Definition von
map
:Beachten Sie, dass es zwei Parameter hat. Das erste ist Ihre Funktion und das zweite ist implizit. Wenn Sie dieses Implizit nicht angeben, wählt Scala das spezifischste aus verfügbare aus.
Über
breakOut
Also, was ist der Zweck von
breakOut
? Betrachten Sie das Beispiel für die Frage: Sie nehmen eine Liste von Zeichenfolgen, wandeln jede Zeichenfolge in ein Tupel um(Int, String)
und erstellen dann eineMap
Out. Der naheliegendste Weg, dies zu tun, wäre, eine Zwischensammlung zu erstellenList[(Int, String)]
und diese dann zu konvertieren.Wäre es angesichts der
map
Verwendung von aBuilder
zur Erstellung der resultierenden Sammlung nicht möglich, den Vermittler zu überspringenList
und die Ergebnisse direkt in a zu sammelnMap
? Offensichtlich ja. Dazu müssen wir jedoch ein EigenesCanBuildFrom
an übergebenmap
, und das ist genau das, was derbreakOut
Fall ist.Schauen wir uns also die Definition von an
breakOut
:Beachten Sie, dass dies
breakOut
parametrisiert ist und eine Instanz von zurückgibtCanBuildFrom
. Wie es passiert, die TypenFrom
,T
undTo
haben bereits geschlossen worden, weil wir wissen , dassmap
erwartet wirdCanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Deshalb:Lassen Sie uns abschließend das von sich
breakOut
selbst empfangene Implizit untersuchen . Es ist vom TypCanBuildFrom[Nothing,T,To]
. Wir kennen bereits alle diese Typen, sodass wir feststellen können, dass wir einen impliziten Typ benötigenCanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Aber gibt es eine solche Definition?Schauen wir uns die
CanBuildFrom
Definition an:So
CanBuildFrom
ist Gegenvariante auf seinem ersten Typparameter. DaNothing
es sich um eine unterste Klasse handelt (dh um eine Unterklasse von allem), bedeutet dies, dass jede Klasse anstelle von verwendet werden kannNothing
.Da es einen solchen Builder gibt, kann Scala damit die gewünschte Ausgabe erzeugen.
Über Bauherren
Viele Methoden aus der Sammlungsbibliothek von Scala bestehen darin, die ursprüngliche Sammlung zu übernehmen und sie irgendwie zu verarbeiten (im Fall von
map
Transformation jedes Elements) und die Ergebnisse in einer neuen Sammlung zu speichern.Um die Wiederverwendung von Code zu maximieren, erfolgt diese Speicherung der Ergebnisse über einen Builder (
scala.collection.mutable.Builder
), der grundsätzlich zwei Vorgänge unterstützt: Anhängen von Elementen und Zurückgeben der resultierenden Auflistung. Der Typ dieser resultierenden Sammlung hängt vom Typ des Builders ab. EinList
Builder gibt also a zurückList
, einMap
Builder gibt a zurückMap
und so weiter. Die Implementierung dermap
Methode muss sich nicht mit der Art des Ergebnisses befassen: Der Builder kümmert sich darum.Auf der anderen Seite bedeutet das
map
, dass dieser Builder irgendwie empfangen werden muss. Das Problem beim Entwerfen von Scala 2.8-Sammlungen bestand darin, den bestmöglichen Builder auszuwählen. Wenn ich zum Beispiel schreiben würdeMap('a' -> 1).map(_.swap)
, würde ich gerne eine Rückerstattung bekommenMap(1 -> 'a')
. Auf der anderen SeiteMap('a' -> 1).map(_._1)
kann a a nicht zurückgebenMap
(es gibt ein zurückIterable
).Die Magie,
Builder
aus den bekannten Ausdruckstypen das bestmögliche zu erzeugen, wird durch diesesCanBuildFrom
Implizit ausgeführt.Über
CanBuildFrom
Um besser zu erklären, was los ist, gebe ich ein Beispiel, in dem die zugeordnete Sammlung eine
Map
anstelle einer istList
. Ich werdeList
später darauf zurückkommen . Betrachten Sie zunächst diese beiden Ausdrücke:Der erste gibt a zurück
Map
und der zweite gibt a zurückIterable
. Die Magie der Rückgabe einer passenden Sammlung liegt in der Arbeit vonCanBuildFrom
. Betrachten wir die Definition von nochmap
einmal, um sie zu verstehen.Die Methode
map
wird von geerbtTraversableLike
. Es wird aufB
und parametrisiert undThat
verwendet die TypparameterA
undRepr
, die die Klasse parametrisieren. Sehen wir uns beide Definitionen zusammen an:Die Klasse
TraversableLike
ist definiert als:Um zu verstehen, woher
A
und woher sieRepr
kommen, betrachten wir die Definition von sichMap
selbst:Weil
TraversableLike
es von allen Merkmalen geerbt wird, die sich erstreckenMap
,A
undRepr
von jedem von ihnen geerbt werden könnte. Der letzte bekommt jedoch die Präferenz. Nach der Definition des UnveränderlichenMap
und aller Merkmale, mit denen es verbunden istTraversableLike
, haben wir also:Wenn Sie die Typparameter
Map[Int, String]
entlang der gesamten Kette übergeben, stellen wir fest, dass die Typen, die an übergebenTraversableLike
werden und daher von verwendet werdenmap
, folgende sind:Zurück zum Beispiel: Die erste Karte empfängt eine Funktion vom Typ
((Int, String)) => (Int, Int)
und die zweite Karte empfängt eine Funktion vom Typ((Int, String)) => String
. Ich benutze die doppelte Klammer, um zu betonen, dass es sich um ein empfangenes Tupel handelt,A
wie wir es gesehen haben.Betrachten wir mit diesen Informationen die anderen Typen.
Wir können sehen , dass der Typ von der ersten zurück
map
istMap[Int,Int]
, und das zweite istIterable[String]
. Wenn man sichmap
die Definition ansieht, ist leicht zu erkennen, dass dies die Werte von sindThat
. Aber woher kommen sie?Wenn wir uns die Begleitobjekte der beteiligten Klassen ansehen, sehen wir einige implizite Deklarationen, die sie bereitstellen. Auf Objekt
Map
:Und auf Objekt
Iterable
, dessen Klasse erweitert wird durchMap
:Diese Definitionen bieten Fabriken zur Parametrisierung
CanBuildFrom
.Scala wählt das spezifischste verfügbare Implizit aus. Im ersten Fall war es der erste
CanBuildFrom
. Im zweiten Fall, da der erste nicht übereinstimmte, wählte er den zweitenCanBuildFrom
.Zurück zur Frage
Sehen wir uns den Code für die Frage,
List
diemap
Definition von 'und ' (noch einmal) an, um zu sehen, wie die Typen abgeleitet werden:Der Typ von
List("London", "Paris")
istList[String]
, also sind die TypenA
undRepr
definiert aufTraversableLike
:Der Typ für
(x => (x.length, x))
ist(String) => (Int, String)
, also ist der Typ vonB
:Der letzte unbekannte Typ
That
ist der Typ des Ergebnisses vonmap
, und das haben wir auch schon:So,
Das bedeutet
breakOut
, dass unbedingt ein Typ oder Subtyp von zurückgegeben werden mussCanBuildFrom[List[String], (Int, String), Map[Int, String]]
.quelle
Ich möchte auf Daniels Antwort aufbauen. Es war sehr gründlich, aber wie in den Kommentaren erwähnt, erklärt es nicht, was Breakout bewirkt.
Entnommen aus Re: Unterstützung für explizite Builder (23.10.2009) , glaube ich, dass Breakout Folgendes tut:
Es gibt dem Compiler einen Vorschlag, welchen Builder er implizit auswählen soll (im Wesentlichen kann der Compiler auswählen, welche Factory seiner Meinung nach am besten zur Situation passt.)
Siehe zum Beispiel Folgendes:
Sie können sehen, dass der Rückgabetyp vom Compiler implizit so ausgewählt wird, dass er dem erwarteten Typ am besten entspricht. Je nachdem, wie Sie die empfangende Variable deklarieren, erhalten Sie unterschiedliche Ergebnisse.
Das Folgende wäre eine äquivalente Möglichkeit, einen Builder anzugeben. Beachten Sie in diesem Fall, dass der Compiler den erwarteten Typ basierend auf dem Typ des Builders ableitet:
quelle
breakOut
" heißt? Ich denke, etwas wieconvert
oderbuildADifferentTypeOfCollection
(aber kürzer) könnte leichter zu merken gewesen sein.Daniel Sobrals Antwort ist großartig und sollte zusammen mit Architecture of Scala Collections (Kapitel 25 der Programmierung in Scala) gelesen werden .
Ich wollte nur näher erläutern, warum es heißt
breakOut
:Warum heißt es
breakOut
?Weil wir von einem Typ in einen anderen ausbrechen wollen :
Aus welchem Typ in welchen Typ ausbrechen? Schauen wir uns die
map
FunktionSeq
als Beispiel an:Wenn wir eine Map direkt aus der Zuordnung über die Elemente einer Sequenz erstellen möchten, wie z.
Der Compiler würde sich beschweren:
Der Grund dafür ist, dass Seq nur weiß, wie man eine andere Seq erstellt (dh es ist eine implizite
CanBuildFrom[Seq[_], B, Seq[B]]
Builder-Factory verfügbar, aber es gibt KEINE Builder-Factory von Seq bis Map).Um zu kompilieren, müssen wir irgendwie
breakOut
die Typanforderung erfüllen und in der Lage sein, einen Builder zu erstellen, der eine Map für diemap
zu verwendende Funktion erstellt.Wie Daniel erklärt hat, hat breakOut die folgende Signatur:
Nothing
ist eine Unterklasse aller Klassen, sodass jede Builder-Fabrik anstelle von ersetzt werden kannimplicit b: CanBuildFrom[Nothing, T, To]
. Wenn wir die breakOut-Funktion verwendet haben, um den impliziten Parameter bereitzustellen:Es würde kompilieren, weil
breakOut
es in der Lage ist, den erforderlichen Typ von bereitzustellenCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
, während der Compiler in der Lage ist, eine implizite Builder-Factory vom TypCanBuildFrom[Map[_, _], (A, B), Map[A, B]]
anstelle von zu findenCanBuildFrom[Nothing, T, To]
breakOut finden kann, um den tatsächlichen Builder zu erstellen.Beachten Sie, dass dies
CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
in Map definiert ist und einfach eine initiiert,MapBuilder
die eine zugrunde liegende Map verwendet.Hoffe das klärt die Dinge auf.
quelle
Ein einfaches Beispiel, um zu verstehen, was
breakOut
bedeutet:quelle
val seq:Seq[Int] = set.map(_ % 2).toVector
Sie nicht die wiederholten Werte, da dieSet
für die beibehalten wurdenmap
.set.map(_ % 2)
erstellt eineSet(1, 0)
erste, die dann in eine konvertiert wirdVector(1, 0)
.