Dans Scala 2.8 , il y a un objet dans scala.collection.package.scala
:
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()
}
On m'a dit que cela se traduit par:
> import scala.collection.breakOut
> val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)
map: Map[Int,String] = Map(6 -> London, 5 -> Paris)
Qu'est-ce qui se passe ici? Pourquoi est breakOut
-il appelé comme argument pour mon List
?
scala
scala-2.8
scala-collections
oxbow_lakes
la source
la source
List
, mais pourmap
.Réponses:
La réponse se trouve sur la définition de
map
: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 uneMap
. La façon la plus évidente de le faire serait de produire uneList[(Int, String)]
collection intermédiaire , puis de la convertir.Étant donné que
map
utilise unBuilder
pour produire la collection résultante, ne serait-il pas possible de sauter l'intermédiaireList
et de collecter les résultats directement dans unMap
? Évidemment, oui. Pour ce faire, cependant, nous devons passer un bonCanBuildFrom
àmap
, et c'est exactement ce quibreakOut
fait.Examinons donc la définition de
breakOut
:Notez qu'il
breakOut
est paramétré et qu'il renvoie une instance deCanBuildFrom
. En l'occurrence, les typesFrom
,T
etTo
ont déjà été déduits, parce que nous savons que celamap
attendCanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Par conséquent:Pour conclure, examinons l'implicite reçu par
breakOut
lui-même. C'est de typeCanBuildFrom[Nothing,T,To]
. Nous connaissons déjà tous ces types, nous pouvons donc déterminer que nous avons besoin d'un implicite de typeCanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Mais existe-t-il une telle définition?Regardons la
CanBuildFrom
définition de:Il en
CanBuildFrom
est de même de la contre-variante sur son premier paramètre de type. Parce queNothing
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 deNothing
.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, unList
générateur renverra unList
, unMap
générateur renverra unMap
, et ainsi de suite. La mise en œuvre de lamap
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 écrireMap('a' -> 1).map(_.swap)
, j'aimerais récupérerMap(1 -> 'a')
. En revanche, unMap('a' -> 1).map(_._1)
ne peut pas retourner unMap
(il renvoie unIterable
).La magie de produire le meilleur possible à
Builder
partir des types d'expression connus s'exerce à travers cetCanBuildFrom
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'unList
. J'y reviendrai plusList
tard. Pour l'instant, considérons ces deux expressions:Le premier retourne un
Map
et le second retourne unIterable
. La magie du retour d'une collection appropriée est l'œuvre deCanBuildFrom
. Examinonsmap
à nouveau la définition de pour la comprendre.La méthode
map
est héritée deTraversableLike
. Il est paramétré surB
etThat
, et utilise les paramètres de typeA
etRepr
, qui paramètrent la classe. Voyons ensemble les deux définitions:La classe
TraversableLike
est définie comme:Pour comprendre d'où
A
et d' où nousRepr
venons, considérons la définition deMap
lui-même:Parce que
TraversableLike
est héritée par tous les traits qui se prolongentMap
,A
etRepr
pourrait être hérité de l' un d'eux. Le dernier a cependant la préférence. Donc, suivant la définition de l'immuableMap
et de tous les traits qui le relientTraversableLike
, nous avons: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 parmap
, sont: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 celuiA
que nous avons vu.Avec ces informations, considérons les autres types.
Nous pouvons voir que le type retourné par le premier
map
estMap[Int,Int]
, et le second estIterable[String]
. En regardantmap
la définition de, il est facile de voir que ce sont les valeurs deThat
. 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
:Et sur objet
Iterable
, dont la classe est étendue parMap
: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 secondCanBuildFrom
.Retour à la question
Voyons le code de la définition de la question
List
etmap
de (encore) pour voir comment les types sont inférés:Le type de
List("London", "Paris")
estList[String]
, donc les typesA
etRepr
définis surTraversableLike
sont:Le type de
(x => (x.length, x))
est(String) => (Int, String)
donc le type deB
est:Le dernier type inconnu
That
est le type du résultat demap
, et nous l'avons déjà aussi:Alors,
Cela signifie qu'il
breakOut
doit nécessairement renvoyer un type ou un sous-type deCanBuildFrom[List[String], (Int, String), Map[Int, String]]
.la source
Je voudrais m'appuyer sur la réponse de Daniel. C'était très approfondi, mais comme indiqué dans les commentaires, cela n'explique pas ce que fait l'évasion.
Tiré de Re: Support for explicit Builders (2009-10-23), voici ce que je pense que la répartition fait:
Il donne au compilateur une suggestion quant au constructeur à choisir implicitement (il permet essentiellement au compilateur de choisir quelle usine, selon lui, convient le mieux à la situation.)
Par exemple, consultez les informations suivantes:
Vous pouvez voir que le type de retour est implicitement choisi par le compilateur pour correspondre au mieux au type attendu. Selon la façon dont vous déclarez la variable de réception, vous obtenez des résultats différents.
Ce qui suit serait une manière équivalente de spécifier un générateur. Notez dans ce cas, le compilateur déduira le type attendu en fonction du type du générateur:
la source
breakOut
"? Je pense que quelque chose commeconvert
oubuildADifferentTypeOfCollection
(mais plus court) aurait été plus facile à retenir.La réponse de Daniel Sobral est excellente et doit être lue conjointement avec Architecture of Scala Collections (Chapitre 25 de Programmation en Scala).
Je voulais juste expliquer pourquoi on l'appelle
breakOut
:Pourquoi est-il appelé
breakOut
?Parce que nous voulons sortir d'un type et en un autre :
Sortez de quel type en quel type? Regardons la
map
fonctionSeq
comme un exemple:Si nous voulions construire une carte directement à partir du mappage sur les éléments d'une séquence tels que:
Le compilateur se plaindrait:
La raison étant que Seq ne sait que construire une autre Seq (c'est-à-dire qu'il existe une
CanBuildFrom[Seq[_], B, Seq[B]]
fabrique de constructeur implicite disponible, mais il n'y a PAS de fabrique de constructeur de Seq à Map).Afin de compiler, nous devons en quelque sorte
breakOut
de l'exigence de type , et être en mesure de construire un constructeur qui produit une carte pour lamap
fonction à utiliser.Comme Daniel l'a expliqué, breakOut a la signature suivante:
Nothing
est une sous-classe de toutes les classes, donc toute fabrique de constructeur peut être remplacée à la place deimplicit b: CanBuildFrom[Nothing, T, To]
. Si nous avons utilisé la fonction breakOut pour fournir le paramètre implicite:Il compilerait, car
breakOut
est capable de fournir le type requis deCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
, tandis que le compilateur est capable de trouver une fabrique de générateur implicite de typeCanBuildFrom[Map[_, _], (A, B), Map[A, B]]
, à la place deCanBuildFrom[Nothing, T, To]
, pour breakOut à utiliser pour créer le générateur réel.Notez que cela
CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
est défini dans Map, et lance simplement unMapBuilder
qui utilise une Map sous-jacente.Espérons que cela arrange les choses.
la source
Un exemple simple pour comprendre ce que
breakOut
fait:la source
val seq:Seq[Int] = set.map(_ % 2).toVector
vous ne donnera pas les valeurs répétées commeSet
a été conservé pour lamap
.set.map(_ % 2)
crée uneSet(1, 0)
première, qui est ensuite convertie enVector(1, 0)
.