La risposta si trova sulla definizione di map
:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Si noti che ha due parametri. La prima è la tua funzione e la seconda è implicita. Se non fornisci questo implicito, Scala sceglierà il più specifico disponibile.
Di breakOut
Allora, qual è lo scopo breakOut
? Considera l'esempio fornito per la domanda, prendi un elenco di stringhe, trasformi ogni stringa in una tupla (Int, String)
e quindi ne produci una Map
. Il modo più ovvio per farlo produrrebbe una List[(Int, String)]
raccolta intermedia e poi la convertirà.
Dato che map
utilizza a Builder
per produrre la raccolta risultante, non sarebbe possibile saltare l'intermediario List
e raccogliere i risultati direttamente in a Map
? Evidentemente sì, lo è. Per farlo, però, abbiamo bisogno di passare una vera e propria CanBuildFrom
a map
, e questo è esattamente quello che breakOut
fa.
Diamo un'occhiata, quindi, alla definizione di 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()
}
Si noti che breakOut
è parametrizzato e che restituisce un'istanza di CanBuildFrom
. Come spesso accade, i tipi From
, T
e To
sono già state dedurre, perché sappiamo che map
si aspetta CanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Perciò:
From = List[String]
T = (Int, String)
To = Map[Int, String]
Per concludere esaminiamo l'implicito ricevuto da breakOut
solo. È di tipo CanBuildFrom[Nothing,T,To]
. Conosciamo già tutti questi tipi, quindi possiamo determinare che abbiamo bisogno di un tipo implicito CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Ma esiste una tale definizione?
Diamo un'occhiata alla CanBuildFrom
definizione di:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Quindi CanBuildFrom
è una contro-variante sul suo primo parametro di tipo. Perché Nothing
è una classe inferiore (cioè, è una sottoclasse di tutto), ciò significa che qualsiasi classe può essere utilizzata al posto di Nothing
.
Poiché esiste un tale costruttore, Scala può usarlo per produrre l'output desiderato.
Informazioni sui costruttori
Molti metodi della libreria delle collezioni di Scala consistono nel prendere la collezione originale, elaborarla in qualche modo (nel caso di map
, trasformare ciascun elemento) e archiviare i risultati in una nuova collezione.
Per massimizzare il riutilizzo del codice, questa memorizzazione dei risultati viene eseguita tramite un builder ( scala.collection.mutable.Builder
), che sostanzialmente supporta due operazioni: aggiungere elementi e restituire la raccolta risultante. Il tipo di questa raccolta risultante dipenderà dal tipo di builder. Pertanto, un List
builder restituirà a List
, un Map
builder restituirà a Map
e così via. L'implementazione del map
metodo non deve riguardare il tipo di risultato: il costruttore si prende cura di esso.
D'altra parte, ciò significa che map
deve ricevere questo costruttore in qualche modo. Il problema affrontato durante la progettazione delle collezioni Scala 2.8 era come scegliere il miglior costruttore possibile. Ad esempio, se dovessi scrivere Map('a' -> 1).map(_.swap)
, mi piacerebbe avere una Map(1 -> 'a')
risposta. D'altra parte, a Map('a' -> 1).map(_._1)
non può restituire a Map
(restituisce a Iterable
).
La magia di produrre il meglio possibile Builder
dai tipi noti dell'espressione viene eseguita attraverso questo CanBuildFrom
implicito.
Di CanBuildFrom
Per spiegare meglio cosa sta succedendo, fornirò un esempio in cui la raccolta da mappare è a Map
anziché a List
. Tornerò più List
tardi. Per ora, considera queste due espressioni:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
Il primo restituisce a Map
e il secondo restituisce an Iterable
. La magia di restituire una collezione adatta è il lavoro di CanBuildFrom
. Consideriamo di map
nuovo la definizione per capirla.
Il metodo map
è ereditato da TraversableLike
. È parametrizzato su B
e That
, e utilizza i parametri di tipo A
e Repr
, che parametrizzano la classe. Vediamo insieme entrambe le definizioni:
La classe TraversableLike
è definita come:
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Per capire da dove A
e Repr
da dove veniamo, consideriamo la definizione di Map
se stesso:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Perché TraversableLike
viene ereditata da tutti i tratti che si estendono Map
, A
e Repr
potrebbe essere ereditato da nessuno di loro. L'ultimo ottiene la preferenza, però. Quindi, seguendo la definizione dell'immutabile Map
e tutti i tratti che lo collegano TraversableLike
, abbiamo:
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
Se passi i parametri di Map[Int, String]
tipo lungo tutta la catena, scopriamo che i tipi passati a TraversableLike
, e, quindi, utilizzati da map
, sono:
A = (Int,String)
Repr = Map[Int, String]
Tornando all'esempio, la prima mappa riceve una funzione di tipo ((Int, String)) => (Int, Int)
e la seconda mappa riceve una funzione di tipo ((Int, String)) => String
. Uso la doppia parentesi per sottolineare che si tratta di una tupla ricevuta, visto che è il tipo di A
come abbiamo visto.
Con queste informazioni, consideriamo gli altri tipi.
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
Possiamo vedere che il tipo restituito dal primo map
è Map[Int,Int]
e il secondo è Iterable[String]
. Guardando map
la definizione, è facile vedere che questi sono i valori di That
. Ma da dove vengono?
Se guardiamo all'interno degli oggetti compagni delle classi coinvolte, vediamo alcune dichiarazioni implicite che li forniscono. Sull'oggetto Map
:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
E sull'oggetto Iterable
, la cui classe è estesa da Map
:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
Queste definizioni forniscono fabbriche per parametri CanBuildFrom
.
Scala sceglierà l'implicito più specifico disponibile. Nel primo caso, è stato il primo CanBuildFrom
. Nel secondo caso, poiché il primo non corrisponde, ha scelto il secondo CanBuildFrom
.
Torna alla domanda
Vediamo il codice per la domanda, la definizione di " List
e map
" (di nuovo) per vedere come vengono dedotti i tipi:
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
Il tipo di List("London", "Paris")
è List[String]
, quindi i tipi A
e Repr
definiti su TraversableLike
sono:
A = String
Repr = List[String]
Il tipo per (x => (x.length, x))
è (String) => (Int, String)
, quindi il tipo di B
è:
B = (Int, String)
L'ultimo tipo sconosciuto, That
è il tipo del risultato di map
, e già lo abbiamo anche:
val map : Map[Int,String] =
Così,
That = Map[Int, String]
Ciò significa che breakOut
deve necessariamente restituire un tipo o sottotipo di CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.
List
, ma permap
.