La réponse se trouve dans la définition de map
:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Notez qu'il a deux paramètres. Le premier est votre fonction et le second est un implicite. Si vous ne fournissez pas cet implicite, Scala choisira la fonction la plus appropriée. spécifique un disponible.
À propos de breakOut
Alors, quel est le but de breakOut
? Considérons l'exemple donné pour la question, Vous prenez une liste de chaînes de caractères, transformez chaque chaîne en un tuple (Int, String)
et produire ensuite un Map
en sortir. La façon la plus évidente de le faire serait de produire un intermédiaire List[(Int, String)]
et ensuite la convertir.
Étant donné que map
utilise un Builder
pour produire la collection résultante, ne serait-il pas possible de sauter l'intermédiaire List
et recueillir les résultats directement dans un Map
? A l'évidence, oui, c'est le cas. Pour ce faire, cependant, nous devons passer une bonne CanBuildFrom
a map
et c'est exactement ce que breakOut
fait.
Examinons donc la définition de l'expression suivante 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()
}
Notez que breakOut
est paramétrée, et qu'elle renvoie une instance de CanBuildFrom
. Il se trouve que les types From
, T
y To
ont déjà été déduites, parce que nous savons que map
attend CanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Par conséquent :
From = List[String]
T = (Int, String)
To = Map[Int, String]
Pour conclure, examinons l'implicite reçu par breakOut
lui-même. Il est de type CanBuildFrom[Nothing,T,To]
. Nous connaissons déjà tous ces types, nous pouvons donc déterminer que nous avons besoin d'un implicite de type CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Mais existe-t-il une telle définition ?
Regardons CanBuildFrom
La définition de l'UE :
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Alors CanBuildFrom
est contra-variant sur son premier paramètre de type. Parce que Nothing
est une classe inférieure (c'est-à-dire qu'elle est une sous-classe de tout), ce qui signifie que tout peut être utilisée à la place de la classe Nothing
.
Puisqu'un tel constructeur existe, Scala peut l'utiliser pour produire le résultat souhaité.
À propos des constructeurs
De nombreuses méthodes de la bibliothèque de collections de Scala consistent à prendre la collection originale, à la traiter d'une manière ou d'une autre (dans le cas de map
en transformant chaque élément), et en stockant les résultats dans une nouvelle collection.
Afin de maximiser la réutilisation du code, ce stockage des résultats se fait par le biais d'un fichier de type constructeur ( scala.collection.mutable.Builder
), qui supporte essentiellement deux opérations : l'ajout d'éléments et le retour de la collection résultante. Le type de cette collection résultante dépendra du type du constructeur. Ainsi, un List
retournera un List
, a Map
retournera un Map
et ainsi de suite. La mise en œuvre de la map
ne doit pas se préoccuper du type du résultat : le constructeur s'en charge.
D'un autre côté, cela signifie que map
doit recevoir ce constructeur d'une manière ou d'une autre. Le problème rencontré lors de la conception de Scala 2.8 Collections était de savoir comment choisir le meilleur constructeur possible. Par exemple, si je devais écrire Map('a' -> 1).map(_.swap)
J'aimerais avoir un Map(1 -> 'a')
dos. D'autre part, un Map('a' -> 1).map(_._1)
ne peut pas retourner un Map
(il renvoie un Iterable
).
La magie de produire le meilleur possible Builder
à partir des types connus de l'expression est effectuée à travers cette CanBuildFrom
implicite.
À propos de CanBuildFrom
Pour mieux expliquer ce qui se passe, je vais donner un exemple où la collection mise en correspondance est une Map
au lieu d'un List
. Je vais retourner à List
plus tard. Pour l'instant, considérez ces deux expressions :
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
La première renvoie un Map
et la seconde renvoie un Iterable
. La magie du retour d'une collection appropriée est l'œuvre de CanBuildFrom
. Considérons la définition de map
encore une fois pour le comprendre.
La méthode map
est hérité de TraversableLike
. Elle est paramétrée sur B
y That
et utilise les paramètres de type A
y Repr
qui paramètrent la classe. Voyons les deux définitions ensemble :
La classe TraversableLike
est défini comme suit :
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Pour comprendre où A
y Repr
proviennent, considérons la définition de Map
lui-même :
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Parce que TraversableLike
est héritée par tous les traits qui prolongent Map
, A
y Repr
pourrait être hérité de n'importe lequel d'entre eux. Mais c'est le dernier qui a la préférence. Ainsi, en suivant la définition de l'immuable Map
et tous les traits qui le relient à TraversableLike
nous avons :
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
Si vous passez les paramètres de type de Map[Int, String]
tout au long de la chaîne, nous constatons que les types transmis à TraversableLike
et, par conséquent, utilisé par map
sont :
A = (Int,String)
Repr = Map[Int, String]
Pour revenir à l'exemple, la première carte reçoit une fonction de type ((Int, String)) => (Int, Int)
et la seconde carte reçoit une fonction de type ((Int, String)) => String
. J'utilise la double parenthèse pour insister sur le fait qu'il s'agit d'un tuple reçu, car c'est le type d'information que l'on reçoit. A
comme nous l'avons vu.
Avec cette information, examinons les autres types.
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
Nous pouvons voir que le type retourné par le premier map
est Map[Int,Int]
et le second est Iterable[String]
. En regardant map
il est facile de voir que ce sont les valeurs de That
. Mais d'où viennent-ils ?
Si nous regardons à l'intérieur des objets compagnons des classes concernées, nous voyons des déclarations implicites les fournissant. Sur l'objet Map
:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
Et sur l'objet Iterable
dont la classe est prolongée par Map
:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
Ces définitions fournissent des usines pour paramétrer CanBuildFrom
.
Scala choisira l'implicite le plus spécifique disponible. Dans le premier cas, c'est le premier CanBuildFrom
. Dans le second cas, comme le premier ne correspondait pas, il a choisi le second CanBuildFrom
.
Retour à la question
Voyons le code de la question, List
et map
(encore) pour voir comment les types sont déduits :
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
Le type de List("London", "Paris")
est List[String]
donc les types A
y Repr
défini sur TraversableLike
sont :
A = String
Repr = List[String]
Le type de (x => (x.length, x))
est (String) => (Int, String)
donc le type de B
est :
B = (Int, String)
Le dernier type inconnu, That
est le type du résultat de map
et nous l'avons déjà :
val map : Map[Int,String] =
Donc,
That = Map[Int, String]
Cela signifie que breakOut
doit, nécessairement, retourner un type ou un sous-type de CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.