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 maputiliza a Builderpara producir la colección resultante, ¿no sería posible omitir el intermediario Listy recopilar los resultados directamente en a Map? Evidentemente, sí, lo es. Para ello, sin embargo, tenemos que pasar una correcta CanBuildFroma map, y eso es exactamente lo que breakOuthace.
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 breakOutestá parametrizado y que devuelve una instancia de CanBuildFrom. Como sucede, los tipos From, Ty Toya se han inferido, porque sabemos que mapestá 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 breakOutsí 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 CanBuildFromdefinición de:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Entonces, CanBuildFromes contra-variante en su primer parámetro de tipo. Debido a que Nothinges 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 maptransformar 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 Listconstructor devolverá a List, un Mapconstructor devolverá a Map, y así sucesivamente. La implementación del mapmétodo no tiene que preocuparse por el tipo de resultado: el constructor se encarga de ello.
Por otro lado, eso significa que mapnecesita 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 Builderde los tipos conocidos de la expresión se realiza a través de este CanBuildFromimplí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 Maplugar de un List. Regresaré a Listmá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 Mapel segundo devuelve un Iterable. La magia de devolver una colección adecuada es obra de CanBuildFrom. Consideremos mapnuevamente la definición de para entenderla.
El método mapse hereda de TraversableLike. Se parametriza en By That, y utiliza los parámetros de tipo Ay Repr, que parametrizan la clase. Veamos ambas definiciones juntas:
La clase TraversableLikese 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 Ay de dónde Reprviene, consideremos la definición de Mapsí mismo:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Porque TraversableLikees heredado por todos los rasgos que se extienden Map, Ay Reprpodría heredarse de cualquiera de ellos. Sin embargo, el último obtiene la preferencia. Entonces, siguiendo la definición de lo inmutable Mapy 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 TraversableLikey, 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 Alo 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 mapes Map[Int,Int], y el segundo es Iterable[String]. Mirando mapla 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 Ay Reprdefinidos en TraversableLikeson:
A = String
Repr = List[String]
El tipo para (x => (x.length, x))es (String) => (Int, String), entonces el tipo de Bes:
B = (Int, String)
El último tipo desconocido Thates 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.