La réponse se trouve sur 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 implicite. Si vous ne fournissez pas cela implicitement, Scala choisira celui le plus spécifique disponible.
À propos breakOut
Alors, quel est le but de breakOut
? Considérez l'exemple donné pour la question, vous prenez une liste de chaînes, transformez chaque chaîne en un tuple (Int, String)
, puis produisez-en une Map
. La façon la plus évidente de le faire serait de produire une List[(Int, String)]
collection intermédiaire , puis de 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 de collecter les résultats directement dans un Map
? Évidemment, oui. Pour ce faire, cependant, nous devons passer un bon CanBuildFrom
à map
, et c'est exactement ce qui breakOut
fait.
Examinons donc la définition de 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 qu'il breakOut
est paramétré et qu'il renvoie une instance de CanBuildFrom
. En l'occurrence, les types From
, T
et To
ont déjà été déduits, parce que nous savons que cela 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. C'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 la CanBuildFrom
définition de:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Il en CanBuildFrom
est de même de la contre-variante sur son premier paramètre de type. Parce que Nothing
c'est une classe inférieure (c'est-à-dire, c'est une sous-classe de tout), cela signifie que n'importe quelle classe peut être utilisée à la place de Nothing
.
Puisqu'un tel générateur existe, Scala peut l'utiliser pour produire la sortie souhaitée.
À propos des constructeurs
De nombreuses méthodes de la bibliothèque de collections de Scala consistent à prendre la collection d'origine, à la traiter d'une manière ou d'une autre (dans le cas de map
, transformer chaque élément) et à stocker les résultats dans une nouvelle collection.
Pour maximiser la réutilisation du code, ce stockage des résultats se fait via un générateur ( scala.collection.mutable.Builder
), qui prend essentiellement en charge 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 générateur. Ainsi, un List
générateur renverra un List
, un Map
générateur renverra un Map
, et ainsi de suite. La mise en œuvre de la map
méthode n'a pas à se préoccuper du type de résultat: le constructeur s'en charge.
D'un autre côté, cela signifie qu'il map
faut en quelque sorte recevoir ce constructeur. Le problème rencontré lors de la conception des collections Scala 2.8 était de savoir comment choisir le meilleur constructeur possible. Par exemple, si je devais écrire Map('a' -> 1).map(_.swap)
, j'aimerais récupérer Map(1 -> 'a')
. En revanche, 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 d'expression connus s'exerce à travers cet CanBuildFrom
implicite.
À propos CanBuildFrom
Pour mieux expliquer ce qui se passe, je vais donner un exemple où la collection en cours de mappage est un Map
au lieu d'un List
. J'y reviendrai plus List
tard. Pour l'instant, considérons ces deux expressions:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
Le premier retourne un Map
et le second retourne un Iterable
. La magie du retour d'une collection appropriée est l'œuvre de CanBuildFrom
. Examinons map
à nouveau la définition de pour la comprendre.
La méthode map
est héritée de TraversableLike
. Il est paramétré sur B
et That
, et utilise les paramètres de type A
et Repr
, qui paramètrent la classe. Voyons ensemble les deux définitions:
La classe TraversableLike
est définie comme:
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 d'où A
et d' où nous Repr
venons, 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 se prolongent Map
, A
et Repr
pourrait être hérité de l' un d'eux. Le dernier a cependant la préférence. Donc, suivant la définition de l'immuable Map
et de 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 Map[Int, String]
tout au long de la chaîne, nous constatons que les types passés à TraversableLike
, et donc utilisés 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 souligner qu'il s'agit d'un tuple reçu, car c'est le type de celui A
que nous avons vu.
Avec ces informations, considérons 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
la définition de, 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 impliquées, nous voyons quelques déclarations implicites les fournissant. Sur l'objet Map
:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
Et sur objet Iterable
, dont la classe est étendue par Map
:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
Ces définitions fournissent des usines pour paramétré CanBuildFrom
.
Scala choisira l'implicite disponible le plus spécifique. Dans le premier cas, c'était le premier CanBuildFrom
. Dans le second cas, le premier ne correspondant pas, il a choisi le second CanBuildFrom
.
Retour à la question
Voyons le code de la définition de la question List
et map
de (encore) pour voir comment les types sont inférés:
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
et Repr
définis 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à aussi:
val map : Map[Int,String] =
Alors,
That = Map[Int, String]
Cela signifie qu'il breakOut
doit nécessairement renvoyer un type ou un sous-type de CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.
List
, mais pourmap
.