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 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 una Map
. La forma más obvia de hacerlo sería producir una List[(Int, String)]
colección intermedia y luego convertirla.
Dado que map
utiliza a Builder
para producir la colección resultante, ¿no sería posible omitir el intermediario List
y recopilar los resultados directamente en a Map
? Evidentemente, sí, lo es. Para ello, sin embargo, tenemos que pasar una correcta CanBuildFrom
a map
, y eso es exactamente lo que breakOut
hace.
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
sí mismo. Es de tipo CanBuildFrom[Nothing,T,To]
. Ya conocemos todos estos tipos, por lo que podemos determinar que necesitamos un tipo implícito CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. ¿Pero hay tal definición?
Veamos la CanBuildFrom
definición de:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Entonces, CanBuildFrom
es 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 que cualquier clase se puede usar en lugar de Nothing
.
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, un List
constructor devolverá a List
, un Map
constructor devolverá a Map
, y así sucesivamente. La implementación del map
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 escribir Map('a' -> 1).map(_.swap)
, me gustaría recibir un Map(1 -> 'a')
respaldo. Por otro lado, a Map('a' -> 1).map(_._1)
no puede devolver a Map
(devuelve un Iterable
).
La magia de producir lo mejor posible Builder
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á sucediendo, daré un ejemplo donde la colección que se está mapeando es un en Map
lugar de un List
. Regresaré 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)
El primero devuelve ay Map
el segundo devuelve un Iterable
. La magia de devolver una colección adecuada es obra de CanBuildFrom
. Consideremos map
nuevamente la definición de para entenderla.
El método map
se hereda de TraversableLike
. Se parametriza en B
y That
, y utiliza 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 A
y de dónde Repr
viene, consideremos la definición de Map
sí mismo:
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 heredarse de cualquiera de ellos. Sin embargo, el último obtiene la preferencia. Entonces, siguiendo la definición de lo inmutable Map
y todos los rasgos que lo conectan TraversableLike
, tenemos:
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]
toda la cadena, encontramos que los tipos pasados TraversableLike
y, por lo tanto, utilizados por map
, son:
A = (Int,String)
Repr = Map[Int, String]
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 de A
lo que 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 map
la definición de, es fácil ver que estos son los valores de That
. ¿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
:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
Y en objeto Iterable
, cuya clase se extiende por Map
:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
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 segundo CanBuildFrom
.
Volver a la pregunta
Vamos a ver el código de la pregunta, List
'sy map
' s definición (de nuevo) para ver cómo se infieren 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 para (x => (x.length, x))
es (String) => (Int, String)
, entonces el tipo de B
es:
B = (Int, String)
El último tipo desconocido That
es el tipo del resultado de map
, y eso ya lo tenemos también:
val map : Map[Int,String] =
Entonces,
That = Map[Int, String]
Eso significa breakOut
, necesariamente, devolver un tipo o subtipo de CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.
List
, sino paramap
.