223 votes

Scala 2.8 breakout

<p>Au Scala <strong>2.8</strong>, il y a un objet dans <code></code> :<pre><code></code></pre><p>On m’a dit que cela se traduit par :</p><pre><code></code></pre><p>Que se passe-t-il ici ? Pourquoi est <code></code> appelé <em>en tant qu’argument</em> à mon <code></code> ?</p></p>

323voto

Daniel C. Sobral Points 159554

La réponse se trouve sur la définition de l' map:

def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That 

Notez qu'il dispose de deux paramètres. La première est votre fonction et la deuxième est implicite. Si vous ne fournissez pas qu'implicite, Scala va choisir la plus spécifique disponible.

À propos de breakOut

Donc, quel est le but de l' breakOut? Prenons l'exemple donné par la question, Vous prenez une liste de chaînes, de transformer chaque chaîne dans un tuple (Int, String), puis de produire un Map . Le moyen le plus évident de le faire serait de produire un intermédiaire List[(Int, String)] de la collecte, puis de le convertir.

Étant donné qu' map utilise un Builder pour produire de la collection, ne serait-il pas possible de sauter l'intermédiaire List , et de collecter les résultats directement dans une Map? De toute évidence, oui, il est. Pour ce faire, cependant, nous avons besoin de passer un bon CanBuildFrom de map, et c'est exactement ce qu' breakOut .

Regardons, puis, à la définition de l' 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étrable, et qu'elle renvoie une instance d' CanBuildFrom. Comme il arrive, les types d' From, T et To ont déjà été déduites, parce que nous savons que map attendent CanBuildFrom[List[String], (Int, String), Map[Int, String]]. Donc:

From = List[String]
T = (Int, String)
To = Map[Int, String]

Pour conclure, nous allons examiner les implicites reçu par breakOut lui-même. Il est de type CanBuildFrom[Nothing,T,To]. Nous savons déjà tous ces types, de sorte que nous pouvons déterminer que nous avons besoin d'un implicite de type CanBuildFrom[Nothing,(Int,String),Map[Int,String]]. Mais est-il une telle définition?

Regardons CanBuildFroms'définition:

trait CanBuildFrom[-From, -Elem, +To] 
extends AnyRef

Donc, CanBuildFrom est contre-variante sur son premier paramètre de type. Parce qu' Nothing est un fond de classe (c'est à dire, il est une sous-classe de tout), cela signifie que toute la classe peut être utilisé à la place de Nothing.

Depuis un tel générateur existe, Scala pouvez l'utiliser pour produire le résultat désiré.

Sur Les Constructeurs

Un grand nombre de méthodes de Scala collections de la bibliothèque se compose de la collection originale, le traitement c'est en quelque sorte (dans le cas d' map, transformant chaque élément), et de stocker les résultats dans une nouvelle collection.

Pour maximiser la réutilisation de code, ce stockage des résultats se fait par l'intermédiaire d'un générateur (scala.collection.mutable.Builder), ce qui en fait prend en charge deux opérations: ajout d'éléments, et le retour de la collection. Le type de cette collection résultante dépend de la nature du constructeur. Ainsi, un List générateur de retourner un List, Map générateur de retourner un Map, et ainsi de suite. La mise en œuvre de l' map méthode n'a pas besoin de se préoccuper du type de résultat: le constructeur prend soin d'elle.

D'autre part, cela signifie qu' map a besoin de recevoir ce constructeur en quelque sorte. Le problème qui se pose lors de la conception de la Scala 2.8 Collections est de savoir comment choisir le meilleur générateur de possible. Par exemple, si je devais écrire Map('a' -> 1).map(_.swap), j'aimerais obtenir un Map(1 -> 'a') de retour. D'autre part, une Map('a' -> 1).map(_._1) ne pouvez pas renvoyer Map (elle renvoie un Iterable).

La magie de produire le meilleur possible Builder de la types connus de l'expression est exécutée par le biais de ce CanBuildFrom implicite.

À propos de CanBuildFrom

Pour mieux expliquer ce qu'il se passe, je vais vous donner un exemple où la collection de mapper est un Map au lieu de List. Je vais retourner à l' List plus tard. Pour l'instant, tenir compte de ces deux expressions:

Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)

La première renvoie une Map et la seconde renvoie à un Iterable. La magie de retour d'un côté de la collection est l'œuvre de l' CanBuildFrom. Considérons la définition de l' map nouveau pour le comprendre.

La méthode map est hérité de l' TraversableLike. Il est paramétrée sur B et That, et rend l'utilisation de paramètres de type A et Repr, ce qui paramétrer la classe. Nous allons voir les deux définitions:

La classe TraversableLike est défini 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 où en A et Repr viennent, nous allons considérer 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 qu' TraversableLike est héritée par tous les traits qui s'étendent Map, A et Repr pourrait être hérité de l'un d'eux. Le dernier obtient la préférence. Donc, d'après la définition de l'immuable Map et tous les traits qui la 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 d' Map[Int, String] tout le chemin en bas de la chaîne, nous constatons que les types passé de TraversableLike, et, ainsi, utilisée par map, sont:

A = (Int,String)
Repr = Map[Int, String]

Revenons à l'exemple, à la première carte de la réception d'une fonction de type ((Int, String)) => (Int, Int) et le deuxième carte est la réception d'une fonction de type ((Int, String)) => Int. J'utilise la double parenthèse pour souligner c'est un n-uplet d'être reçu, comme c'est le type d' A comme nous l'avons vu.

Avec cette information, nous allons examiner les autres types.

map Function.tupled(_ -> _.length):
B = (Int, Int)

map (_._2):
B = Int

Nous pouvons voir que le type renvoyé par la première map est Map[Int,Int], et la deuxième est Iterable[String]. En regardant maps'définition, il est facile de voir que ce sont les valeurs de That. Mais d'où viennent-ils?

Si nous regardons à l'intérieur de la compagne objets des classes concernées, nous voyons certains implicite des déclarations des prestataires. 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étrée CanBuildFrom.

Scala va choisir la plus spécifique implicite disponibles. Dans le premier cas, il a été le premier CanBuildFrom. Dans le second cas, que le premier n'a pas de match, il a choisi la seconde, CanBuildFrom.

Retour à la Question

Voyons le code de la question, Lists et maps'définition (encore une fois) pour voir comment les types sont présumées:

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 d' List("London", "Paris") est List[String], de sorte que les types d' A et Repr défini sur TraversableLike sont:

A = String
Repr = List[String]

Le type (x => (x.length, x)) est (String) => (Int, String), de sorte que le type d' B est:

B = (Int, String)

Le dernier type inconnu, That est le type du résultat de l' map, et nous avons déjà qu'ainsi:

val map : Map[Int,String] =

Donc,

That = Map[Int, String]

Cela signifie que breakOut doit, nécessairement, de retour d'un type ou sous-type d' CanBuildFrom[List[String], (Int, String), Map[Int, String]].

86voto

Austen Holmes Points 561

J'aimerais revenir sur la réponse de Daniel. Il est très complet, mais comme indiqué dans les commentaires, il n'explique pas ce breakout.

Pris de Re: Soutien explicite des Constructeurs (2009-10-23), voici ce que je crois breakout n':

Il donne le compilateur une suggestion à laquelle le Générateur de choisir implicitement (essentiellement, il permet au compilateur de choisir usine il pense s'adapte le mieux à la situation.)

Voir, par exemple, les suivantes:

scala> import scala.collection.generic._
import scala.collection.generic._

scala> import scala.collection._
import scala.collection._

scala> import scala.collection.mutable._
import scala.collection.mutable._

scala>

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()
     |    }
breakOut: [From, T, To]
     |    (implicit b: scala.collection.generic.CanBuildFrom[Nothing,T,To])
     |    java.lang.Object with
     |    scala.collection.generic.CanBuildFrom[From,T,To]

scala> val l = List(1, 2, 3)
l: List[Int] = List(1, 2, 3)

scala> val imp = l.map(_ + 1)(breakOut)
imp: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 4)

scala> val arr: Array[Int] = l.map(_ + 1)(breakOut)
imp: Array[Int] = Array(2, 3, 4)

scala> val stream: Stream[Int] = l.map(_ + 1)(breakOut)
stream: Stream[Int] = Stream(2, ?)

scala> val seq: Seq[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Seq[Int] = ArrayBuffer(2, 3, 4)

scala> val set: Set[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Set[Int] = Set(2, 4, 3)

scala> val hashSet: HashSet[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.HashSet[Int] = Set(2, 4, 3)

Vous pouvez voir le type de retour est implicitement choisie par le compilateur afin de mieux faire correspondre le type attendu. Selon la façon dont vous déclarer la réception de la variable, vous obtenez des résultats différents.

La suite serait un équivalent de la manière d'indiquer un constructeur. Remarque dans ce cas, le compilateur déduit le type attendu basé sur le constructeur du type:

scala> def buildWith[From, T, To](b : Builder[T, To]) =
     |    new CanBuildFrom[From, T, To] {
     |      def apply(from: From) = b ; def apply() = b
     |    }
buildWith: [From, T, To]
     |    (b: scala.collection.mutable.Builder[T,To])
     |    java.lang.Object with
     |    scala.collection.generic.CanBuildFrom[From,T,To]

scala> val a = l.map(_ + 1)(buildWith(Array.newBuilder[Int]))
a: Array[Int] = Array(2, 3, 4)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by: