Die Antwort findet sich in der Definition von map
:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
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 erstellen List[(Int, String)]
und diese dann zu konvertieren.
Wäre es angesichts der map
Verwendung von a Builder
zur Erstellung der resultierenden Sammlung nicht möglich, den Vermittler zu überspringen List
und die Ergebnisse direkt in a zu sammeln Map
? Offensichtlich ja. Dazu müssen wir jedoch ein Eigenes CanBuildFrom
an übergebenmap
, und das ist genau das, was der breakOut
Fall ist.
Schauen wir uns also die Definition von an 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()
}
Beachten Sie, dass dies breakOut
parametrisiert ist und eine Instanz von zurückgibt CanBuildFrom
. Wie es passiert, die Typen From
, T
und To
haben bereits geschlossen worden, weil wir wissen , dass map
erwartet wird CanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Deshalb:
From = List[String]
T = (Int, String)
To = Map[Int, String]
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ötigen CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Aber gibt es eine solche Definition?
Schauen wir uns die CanBuildFrom
Definition an:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
So CanBuildFrom
ist Gegenvariante auf seinem ersten Typparameter. Da Nothing
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. Ein List
Builder gibt also a zurück List
, ein Map
Builder gibt a zurückMap
und so weiter. Die Implementierung der map
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ürde Map('a' -> 1).map(_.swap)
, würde ich gerne eine Rückerstattung bekommen Map(1 -> 'a')
. Auf der anderen Seite Map('a' -> 1).map(_._1)
kann a a nicht zurückgeben Map
(es gibt ein zurückIterable
).
Die Magie, Builder
aus den bekannten Ausdruckstypen das bestmögliche zu erzeugen, wird durch dieses CanBuildFrom
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 ist List
. Ich werde List
später darauf zurückkommen . Betrachten Sie zunächst diese beiden Ausdrücke:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
Der erste gibt a zurück Map
und der zweite gibt a zurück Iterable
. Die Magie der Rückgabe einer passenden Sammlung liegt in der Arbeit von CanBuildFrom
. Betrachten wir die Definition von noch map
einmal, um sie zu verstehen.
Die Methode map
wird von geerbt TraversableLike
. Es wird auf B
und parametrisiert und That
verwendet die Typparameter A
und Repr
, die die Klasse parametrisieren. Sehen wir uns beide Definitionen zusammen an:
Die Klasse TraversableLike
ist definiert als:
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Um zu verstehen, woher A
und woher sie Repr
kommen, betrachten wir die Definition von sich Map
selbst:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Weil TraversableLike
es von allen Merkmalen geerbt wird, die sich erstrecken Map
, A
und Repr
von jedem von ihnen geerbt werden könnte. Der letzte bekommt jedoch die Präferenz. Nach der Definition des Unveränderlichen Map
und aller Merkmale, mit denen es verbunden ist TraversableLike
, haben wir also:
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
Wenn Sie die Typparameter Map[Int, String]
entlang der gesamten Kette übergeben, stellen wir fest, dass die Typen, die an übergeben TraversableLike
werden und daher von verwendet werden map
, folgende sind:
A = (Int,String)
Repr = Map[Int, String]
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.
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
Wir können sehen , dass der Typ von der ersten zurück map
ist Map[Int,Int]
, und das zweite ist Iterable[String]
. Wenn man sich map
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
:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
Und auf Objekt Iterable
, dessen Klasse erweitert wird durch Map
:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
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 zweiten CanBuildFrom
.
Zurück zur Frage
Sehen wir uns den Code für die Frage, List
die map
Definition von 'und ' (noch einmal) an, um zu sehen, wie die Typen abgeleitet werden:
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
Der Typ von List("London", "Paris")
ist List[String]
, also sind die Typen A
und Repr
definiert auf TraversableLike
:
A = String
Repr = List[String]
Der Typ für (x => (x.length, x))
ist (String) => (Int, String)
, also ist der Typ von B
:
B = (Int, String)
Der letzte unbekannte Typ That
ist der Typ des Ergebnisses von map
, und das haben wir auch schon:
val map : Map[Int,String] =
So,
That = Map[Int, String]
Das bedeutet breakOut
, dass unbedingt ein Typ oder Subtyp von zurückgegeben werden muss CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.
List
, sondern fürmap
.