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 maputilizza a Builderper produrre la raccolta risultante, non sarebbe possibile saltare l'intermediario Liste raccogliere i risultati direttamente in a Map? Evidentemente sì, lo è. Per farlo, però, abbiamo bisogno di passare una vera e propria CanBuildFroma map, e questo è esattamente quello che breakOutfa.
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, Te Tosono già state dedurre, perché sappiamo che mapsi 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 breakOutsolo. È 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 CanBuildFromdefinizione 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 Listbuilder restituirà a List, un Mapbuilder restituirà a Mape così via. L'implementazione del mapmetodo non deve riguardare il tipo di risultato: il costruttore si prende cura di esso.
D'altra parte, ciò significa che mapdeve 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 Builderdai tipi noti dell'espressione viene eseguita attraverso questo CanBuildFromimplicito.
Di CanBuildFrom
Per spiegare meglio cosa sta succedendo, fornirò un esempio in cui la raccolta da mappare è a Mapanziché a List. Tornerò più Listtardi. 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 Mape il secondo restituisce an Iterable. La magia di restituire una collezione adatta è il lavoro di CanBuildFrom. Consideriamo di mapnuovo la definizione per capirla.
Il metodo mapè ereditato da TraversableLike. È parametrizzato su Be That, e utilizza i parametri di tipo Ae 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 Ae Reprda dove veniamo, consideriamo la definizione di Mapse stesso:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Perché TraversableLikeviene ereditata da tutti i tratti che si estendono Map, Ae Reprpotrebbe essere ereditato da nessuno di loro. L'ultimo ottiene la preferenza, però. Quindi, seguendo la definizione dell'immutabile Mape 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 Acome 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 mapla 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 " Liste 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 Ae Reprdefiniti su TraversableLikesono:
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 breakOutdeve necessariamente restituire un tipo o sottotipo di CanBuildFrom[List[String], (Int, String), Map[Int, String]].
List, ma permap.