Scala 2.8 breakOut
En Scala 2.8, hay un objeto en 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()
}
Se me ha dicho que esto resulta en:
> 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á pasando aquí? ¿Por qué se llama breakOut
como argumento a mi List
?
4 answers
La respuesta se encuentra en la definición de map
:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Tenga en cuenta que tiene dos parámetros. La primera es su función y la segunda es una implícita. Si no lo proporciona implícito, Scala elegirá el más específico disponible.
Acerca de breakOut
Entonces, ¿cuál es el propósito de breakOut
? Considere el ejemplo dado para la pregunta, Toma una lista de cadenas, transforma cada cadena en una tupla (Int, String)
, y luego produce a Map
fuera de él. La forma más obvia de hacerlo sería producir una colección intermediaria List[(Int, String)]
, y luego convertirla.
Dado que map
utiliza un Builder
para producir la colección resultante, ¿no sería posible omitir el intermediario List
y recopilar los resultados directamente en un Map
? Evidentemente, sí, lo es. Para hacerlo, sin embargo, necesitamos pasar un CanBuildFrom
apropiado a map
, y eso es exactamente lo que hace breakOut
.
Veamos, entonces, la definición 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()
}
Tenga en cuenta que breakOut
está parametrizado, y que devuelve una instancia de CanBuildFrom
. Como sucede, los tipos From
, T
y To
ya se han inferido, porque sabemos que map
está esperando CanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Por lo tanto:
From = List[String]
T = (Int, String)
To = Map[Int, String]
Para concluir examinemos lo implícito recibido por breakOut
en sí. Es de tipo CanBuildFrom[Nothing,T,To]
. Ya conocemos todos estos tipos, por lo que podemos determinar que necesitamos un implícito de tipo CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Pero hay tal definición?
Veamos la definición de CanBuildFrom
:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Así que CanBuildFrom
es una contra-variante en su primer parámetro de tipo. Debido a que Nothing
es una clase inferior (es decir, es una subclase de todo), eso significa cualquier clase puede usarse en lugar de Nothing
.
Dado que tal constructor existe, Scala puede usarlo para producir la salida deseada.
Acerca de los constructores
Muchos métodos de la biblioteca de colecciones de Scala consisten en tomar la colección original, procesándola de alguna manera (en el caso de map
, transformando cada elemento), y almacenando los resultados en una nueva colección.
Para maximizar la reutilización de código, este almacenamiento de resultados se realiza a través de un constructor (scala.collection.mutable.Builder
), que básicamente soporta dos operaciones: anexar elementos y devolver la colección resultante. El tipo de esta colección resultante dependerá del tipo de generador. Por lo tanto, un constructor List
devolverá un constructor List
, un constructor Map
devolverá un Map
, y así sucesivamente. La implementación del método map
no necesita preocuparse por el tipo del resultado: el constructor se encarga de él.
Por otro lado, eso significa que map
necesita recibir este constructor de alguna manera. El problema al diseñar las colecciones de Scala 2.8 fue cómo elegir el mejor constructor posible. Por ejemplo, si tuviera que escribir Map('a' -> 1).map(_.swap)
, me gustaría obtener un Map(1 -> 'a')
de vuelta. Por otro lado, un Map('a' -> 1).map(_._1)
no puede devolver un Map
(devuelve un Iterable
).
La magia de producir lo mejor posible Builder
a partir de los tipos conocidos de la expresión se realiza a través de este CanBuildFrom
implícito.
Acerca de CanBuildFrom
Para explicar mejor lo que está pasando, voy a dar un ejemplo donde la colección que se mapea es un Map
en lugar de un List
. Volveré a List
más tarde. Por ahora, considere estas dos expresiones:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
La primera devuelve un Map
y la segunda devuelve un Iterable
. Magia de devolver una colección adecuada es el trabajo de CanBuildFrom
. Consideremos de nuevo la definición de map
para entenderla.
El método map
se hereda de TraversableLike
. Se parametriza en B
y That
, y hace uso de los parámetros de tipo A
y Repr
, que parametrizan la clase. Veamos ambas definiciones juntas:
La clase TraversableLike
se define como:
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Para entender de dónde vienen A
y Repr
, consideremos la definición de Map
en sí:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Porque TraversableLike
es heredado por todos los rasgos que se extienden Map
, A
y Repr
podría ser heredado de cualquiera de ellos. El último tiene la preferencia, sin embargo. Entonces, siguiendo la definición de lo inmutable Map
y todos los rasgos que lo conectan con TraversableLike
, tenemos: {[116]]}
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 pasa los parámetros de tipo de Map[Int, String]
hasta el final de la cadena, encontramos que los tipos pasados a TraversableLike
, y, por lo tanto, utilizados por map
, son:
A = (Int,String)
Repr = Map[Int, String]
Va volviendo al ejemplo, el primer mapa recibe una función de tipo ((Int, String)) => (Int, Int)
y el segundo mapa recibe una función de tipo ((Int, String)) => String
. Uso el doble paréntesis para enfatizar que es una tupla que se recibe, ya que ese es el tipo de A
como vimos.
Con esa información, consideremos los otros tipos.
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
Podemos ver que el tipo devuelto por el primero map
es Map[Int,Int]
, y el segundo es Iterable[String]
. Mirando la definición de map
, es fácil ver que estos son los valores de That
. Pero ¿de dónde vienen?
Si miramos dentro de los objetos compañeros de las clases involucradas, vemos algunas declaraciones implícitas que los proporcionan. Sobre el objeto Map
:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
Y en el objeto Iterable
, cuya clase se extiende por Map
:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
Estas definiciones proporcionan fábricas para CanBuildFrom
parametrizado.
Scala elegirá el implícito más específico disponible. En el primer caso, fue el primero CanBuildFrom
. En el segundo caso, como el primero no coincidió, eligió el segundo CanBuildFrom
.
Volver a la pregunta
Veamos el código de la pregunta, List
's y map
's definición (de nuevo) para ver cómo se deducen los tipos:
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
El tipo de List("London", "Paris")
es List[String]
, por lo que los tipos A
y Repr
definidos en TraversableLike
son:
A = String
Repr = List[String]
El tipo de (x => (x.length, x))
es (String) => (Int, String)
, por lo que el tipo de B
es:
B = (Int, String)
El último tipo desconocido, That
es el tipo de la resultado de map
, y ya tenemos eso también:
val map : Map[Int,String] =
Así que,
That = Map[Int, String]
Eso significa que breakOut
debe, necesariamente, devolver un tipo o subtipo de CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-27 15:36:25
Me gustaría construir sobre la respuesta de Daniel. Fue muy minucioso, pero como se señaló en los comentarios, no explica lo que hace breakout.
Tomado de Re: Soporte para Constructores explícitos (2009-10-23), esto es lo que creo que breakout hace:
Le da al compilador una sugerencia en cuanto a qué Constructor elegir implícitamente (esencialmente le permite al compilador elegir qué fábrica cree que se ajusta mejor a la situación.)
Por ejemplo, ver el siguiente:
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)
Puede ver que el tipo devuelto es elegido implícitamente por el compilador para que coincida mejor con el tipo esperado. Dependiendo de cómo declare la variable receptora, obtendrá resultados diferentes.
La siguiente sería una forma equivalente de especificar un constructor. Nota en este caso, el compilador inferirá el tipo esperado basado en el tipo del constructor:
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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-06 17:34:03
La respuesta de Daniel Sobral es genial, y debe leerse junto con Architecture of Scala Collections (Capítulo 25 de Programming in Scala).
Solo quería explicar por qué se llama breakOut
:
¿por Qué se llama breakOut
?
Porque queremos salir de un tipo y entrar en otro:
¿Romper de qué tipo en qué tipo? Veamos la función map
en Seq
como un ejemplo:
Seq.map[B, That](f: (A) -> B)(implicit bf: CanBuildFrom[Seq[A], B, That]): That
Si quería construir un Mapa directamente a partir del mapeo sobre los elementos de una secuencia como:
val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))
El compilador se quejaría:
error: type mismatch;
found : Seq[(String, Int)]
required: Map[String,Int]
La razón es que Seq solo sabe cómo construir otro Seq (es decir, hay un builder factory implícito CanBuildFrom[Seq[_], B, Seq[B]]
disponible, pero NO hay NINGÚN builder factory de Seq al Mapa).
Para compilar, necesitamos de alguna manera breakOut
del tipo requisito, y ser capaz de construir un constructor que produce un Asignar la función map
a usar.
Como Daniel ha explicado, breakOut tiene la siguiente firma:
def breakOut[From, T, To](implicit b: CanBuildFrom[Nothing, T, To]): CanBuildFrom[From, T, To] =
// can't just return b because the argument to apply could be cast to From in b
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply()
def apply() = b.apply()
}
Nothing
es una subclase de todas las clases, por lo que cualquier builder factory puede ser sustituido en lugar de implicit b: CanBuildFrom[Nothing, T, To]
. Si usamos la función breakOut para proporcionar el parámetro implícito:
val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))(collection.breakOut)
Compilaría, porque breakOut
es capaz de proporcionar el tipo requerido de CanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
, mientras que el compilador es capaz de encontrar una fábrica de constructor implícita de tipo CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
, en lugar de CanBuildFrom[Nothing, T, To]
, para breakOut a utilizar para crear el constructor real.
Tenga en cuenta que CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
se define en Mapa, y simplemente inicia un MapBuilder
que utiliza un Mapa subyacente.
Espero que esto aclare las cosas.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-30 14:26:24
Un ejemplo sencillo para entender lo que hace breakOut
:
scala> import collection.breakOut
import collection.breakOut
scala> val set = Set(1, 2, 3, 4)
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)
scala> set.map(_ % 2)
res0: scala.collection.immutable.Set[Int] = Set(1, 0)
scala> val seq:Seq[Int] = set.map(_ % 2)(breakOut)
seq: Seq[Int] = Vector(1, 0, 1, 0) // map created a Seq[Int] instead of the default Set[Int]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-29 22:00:30