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()
}
Me han dicho que esto da como resultado:
> 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)
¿Que esta pasando aqui? ¿Por qué se me breakOut
llama como argumento para mi List
?
scala
scala-2.8
scala-collections
oxbow_lakes
fuente
fuente
List
, sino paramap
.Respuestas:
La respuesta se encuentra en la definición de
map
:Tenga en cuenta que tiene dos parámetros. La primera es su función y la segunda es implícita. Si no proporciona esa información implícita, Scala elegirá la más específica disponible.
Acerca de
breakOut
Entonces, ¿para qué sirve
breakOut
? Considere el ejemplo dado para la pregunta: toma una lista de cadenas, transforma cada cadena en una tupla(Int, String)
y luego produce unaMap
. La forma más obvia de hacerlo sería producir unaList[(Int, String)]
colección intermedia y luego convertirla.Dado que
map
utiliza aBuilder
para producir la colección resultante, ¿no sería posible omitir el intermediarioList
y recopilar los resultados directamente en aMap
? Evidentemente, sí, lo es. Para ello, sin embargo, tenemos que pasar una correctaCanBuildFrom
amap
, y eso es exactamente lo quebreakOut
hace.Veamos, entonces, la definición de
breakOut
:Tenga en cuenta que
breakOut
está parametrizado y que devuelve una instancia deCanBuildFrom
. Como sucede, los tiposFrom
,T
yTo
ya se han inferido, porque sabemos quemap
está esperandoCanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Por lo tanto:Para concluir, examinemos lo implícito recibido por
breakOut
sí mismo. Es de tipoCanBuildFrom[Nothing,T,To]
. Ya conocemos todos estos tipos, por lo que podemos determinar que necesitamos un tipo implícitoCanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. ¿Pero hay tal definición?Veamos la
CanBuildFrom
definición de:Entonces,
CanBuildFrom
es contra-variante en su primer parámetro de tipo. Debido a queNothing
es una clase inferior (es decir, es una subclase de todo), eso significa que cualquier clase se puede usar en lugar deNothing
.Dado que existe un generador de este tipo, Scala puede usarlo para producir el resultado deseado.
Sobre constructores
Una gran cantidad de métodos de la biblioteca de colecciones de Scala consiste en tomar la colección original, procesarla de alguna manera (en el caso de
map
transformar cada elemento) y almacenar los resultados en una nueva colección.Para maximizar la reutilización del código, este almacenamiento de resultados se realiza a través de un generador (
scala.collection.mutable.Builder
), que básicamente admite dos operaciones: agregar elementos y devolver la colección resultante. El tipo de esta colección resultante dependerá del tipo del constructor. Por lo tanto, unList
constructor devolverá aList
, unMap
constructor devolverá aMap
, y así sucesivamente. La implementación delmap
método no tiene que preocuparse por el tipo de resultado: el constructor se encarga de ello.Por otro lado, eso significa que
map
necesita recibir este constructor de alguna manera. El problema al diseñar las Colecciones Scala 2.8 fue cómo elegir el mejor constructor posible. Por ejemplo, si tuviera que escribirMap('a' -> 1).map(_.swap)
, me gustaría recibir unMap(1 -> 'a')
respaldo. Por otro lado, aMap('a' -> 1).map(_._1)
no puede devolver aMap
(devuelve unIterable
).La magia de producir lo mejor posible
Builder
de los tipos conocidos de la expresión se realiza a través de esteCanBuildFrom
implícito.Acerca de
CanBuildFrom
Para explicar mejor lo que está sucediendo, daré un ejemplo donde la colección que se está mapeando es un en
Map
lugar de unList
. Regresaré aList
más tarde. Por ahora, considere estas dos expresiones:El primero devuelve ay
Map
el segundo devuelve unIterable
. La magia de devolver una colección adecuada es obra deCanBuildFrom
. Consideremosmap
nuevamente la definición de para entenderla.El método
map
se hereda deTraversableLike
. Se parametriza enB
yThat
, y utiliza los parámetros de tipoA
yRepr
, que parametrizan la clase. Veamos ambas definiciones juntas:La clase
TraversableLike
se define como:Para entender de dónde
A
y de dóndeRepr
viene, consideremos la definición deMap
sí mismo:Porque
TraversableLike
es heredado por todos los rasgos que se extiendenMap
,A
yRepr
podría heredarse de cualquiera de ellos. Sin embargo, el último obtiene la preferencia. Entonces, siguiendo la definición de lo inmutableMap
y todos los rasgos que lo conectanTraversableLike
, tenemos:Si pasa los parámetros de tipo de
Map[Int, String]
toda la cadena, encontramos que los tipos pasadosTraversableLike
y, por lo tanto, utilizados pormap
, son: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
. Utilizo el paréntesis doble para enfatizar que se está recibiendo una tupla, ya que ese es el tipo deA
lo que vimos.Con esa información, consideremos los otros tipos.
Podemos ver que el tipo devuelto por el primero
map
esMap[Int,Int]
, y el segundo esIterable[String]
. Mirandomap
la definición de, es fácil ver que estos son los valores deThat
. ¿Pero de dónde vienen?Si miramos dentro de los objetos complementarios de las clases involucradas, vemos algunas declaraciones implícitas que los proporcionan. En objeto
Map
:Y en objeto
Iterable
, cuya clase se extiende porMap
:Estas definiciones proporcionan fábricas para parametrizadas
CanBuildFrom
.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 coincidía, eligió el segundoCanBuildFrom
.Volver a la pregunta
Vamos a ver el código de la pregunta,
List
'symap
' s definición (de nuevo) para ver cómo se infieren los tipos:El tipo de
List("London", "Paris")
esList[String]
, por lo que los tiposA
yRepr
definidos enTraversableLike
son:El tipo para
(x => (x.length, x))
es(String) => (Int, String)
, entonces el tipo deB
es:El último tipo desconocido
That
es el tipo del resultado demap
, y eso ya lo tenemos también:Entonces,
Eso significa
breakOut
, necesariamente, devolver un tipo o subtipo deCanBuildFrom[List[String], (Int, String), Map[Int, String]]
.fuente
Me gustaría construir sobre la respuesta de Daniel. Fue muy exhaustivo, pero como se señaló en los comentarios, no explica qué hace la ruptura.
Tomado de Re: Soporte para constructores explícitos (23-10-2009), esto es lo que creo que hace la ruptura:
Le da al compilador una sugerencia sobre 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, vea lo siguiente:
Puede ver que el tipo de retorno 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.
Lo siguiente sería una forma equivalente de especificar un constructor. Tenga en cuenta que en este caso, el compilador inferirá el tipo esperado en función del tipo del constructor:
fuente
breakOut
"? Estoy pensando que algo comoconvert
obuildADifferentTypeOfCollection
(pero más corto) podría haber sido más fácil de recordar.La respuesta de Daniel Sobral es excelente, y debe leerse junto con Architecture of Scala Collections (Capítulo 25 de Programación en Scala).
Solo quería explicar por qué se llama
breakOut
:¿Por qué se llama
breakOut
?Porque queremos salir de un tipo y pasar a otro :
¿Salir de qué tipo en qué tipo? Veamos la
map
funciónSeq
como un ejemplo:Si quisiéramos construir un Mapa directamente desde el mapeo sobre los elementos de una secuencia como:
El compilador se quejaría:
La razón es que Seq solo sabe cómo construir otra Seq (es decir, hay una
CanBuildFrom[Seq[_], B, Seq[B]]
fábrica de constructores implícita disponible, pero NO hay una fábrica de constructores desde Seq hasta Map).Para compilar, necesitamos de alguna manera
breakOut
el requisito de tipo , y poder construir un generador que produzca un Mapa para que lamap
función lo use.Como Daniel ha explicado, breakOut tiene la siguiente firma:
Nothing
es una subclase de todas las clases, por lo que cualquier fábrica de constructores puede sustituirse en lugar deimplicit b: CanBuildFrom[Nothing, T, To]
. Si usamos la función breakOut para proporcionar el parámetro implícito:Se compilaría, porque
breakOut
es capaz de proporcionar el tipo requerido deCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
, mientras que el compilador puede encontrar una fábrica de tipo de generador implícitoCanBuildFrom[Map[_, _], (A, B), Map[A, B]]
, en lugar deCanBuildFrom[Nothing, T, To]
, para que breakOut lo use para crear el generador real.Tenga en cuenta que
CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
se define en el Mapa, y simplemente inicia unMapBuilder
que utiliza un Mapa subyacente.Espero que esto aclare las cosas.
fuente
Un ejemplo simple para entender lo que
breakOut
hace:fuente
val seq:Seq[Int] = set.map(_ % 2).toVector
le dará los valores repetidos ya queSet
se conservaron paramap
.set.map(_ % 2)
crea unSet(1, 0)
primero, que luego se convierte en aVector(1, 0)
.