A resposta é encontrada na definição de map:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Observe que ele possui dois parâmetros. A primeira é a sua função e a segunda é implícita. Se você não fornecer esse implícito, o Scala escolherá o mais específico disponível.
Sobre breakOut
Então, qual é o propósito breakOut? Considere o exemplo dado para a pergunta: você pega uma lista de cadeias, transforma cada cadeia em uma tupla (Int, String)e, em seguida, produz um Mapresultado. A maneira mais óbvia de fazer isso produziria uma List[(Int, String)]coleção intermediária e depois a converteria.
Dado que mapusa a Builderpara produzir a coleção resultante, não seria possível pular o intermediário Liste coletar os resultados diretamente em uma Map? Evidentemente, é sim. Para isso, no entanto, é preciso passar por um adequado CanBuildFrompara map, e que é exatamente o que breakOutfaz.
Vejamos, então, a definição 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()
}
Observe que breakOutestá parametrizado e retorna uma instância de CanBuildFrom. Por acaso, os tipos From, Te Tojá foram inferidos, porque sabemos que isso mapé esperado CanBuildFrom[List[String], (Int, String), Map[Int, String]]. Portanto:
From = List[String]
T = (Int, String)
To = Map[Int, String]
Para concluir, vamos examinar o implícito recebido por breakOutsi só. É do tipo CanBuildFrom[Nothing,T,To]. Já conhecemos todos esses tipos, para podermos determinar que precisamos de um tipo implícito CanBuildFrom[Nothing,(Int,String),Map[Int,String]]. Mas existe essa definição?
Vamos dar uma olhada na CanBuildFromdefinição de:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Então, CanBuildFromé contra-variante em seu primeiro parâmetro de tipo. Como Nothingé uma classe inferior (ou seja, é uma subclasse de tudo), isso significa que qualquer classe pode ser usada no lugar de Nothing.
Como esse construtor existe, o Scala pode usá-lo para produzir a saída desejada.
Sobre os construtores
Muitos métodos da biblioteca de coleções do Scala consistem em pegar a coleção original, processá-la de alguma forma (no caso de maptransformar cada elemento) e armazenar os resultados em uma nova coleção.
Para maximizar a reutilização de código, esse armazenamento de resultados é feito por meio de um construtor ( scala.collection.mutable.Builder), que basicamente suporta duas operações: anexar elementos e retornar a coleção resultante. O tipo dessa coleção resultante dependerá do tipo do construtor. Assim, um Listconstrutor retornará a List, um Mapconstrutor retornará a Mape assim por diante. A implementação do mapmétodo não precisa se preocupar com o tipo de resultado: o construtor cuida dele.
Por outro lado, isso significa que mapprecisa receber esse construtor de alguma forma. O problema enfrentado ao projetar o Scala 2.8 Collections foi como escolher o melhor construtor possível. Por exemplo, se eu escrevesse Map('a' -> 1).map(_.swap), gostaria de me Map(1 -> 'a')vingar. Por outro lado, a Map('a' -> 1).map(_._1)não pode retornar a Map(retorna uma Iterable).
A mágica de produzir o melhor possível a Builderpartir dos tipos conhecidos da expressão é realizada por esse CanBuildFromimplícito.
Sobre CanBuildFrom
Para explicar melhor o que está acontecendo, darei um exemplo em que a coleção que está sendo mapeada é um em Mapvez de a List. Eu voltarei Listmais tarde. Por enquanto, considere estas duas expressões:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
O primeiro retorna ae Mapo segundo retorna a Iterable. A mágica de devolver uma coleção apropriada é o trabalho de CanBuildFrom. Vamos considerar a definição de mapnovamente para entendê-la.
O método mapé herdado de TraversableLike. É parametrizado em Be That, e utiliza os parâmetros de tipo Ae Repr, que parametrizam a classe. Vamos ver as duas definições juntas:
A classe TraversableLikeé definida 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 onde Ae de onde Reprvem, vamos considerar a definição em Mapsi:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Porque TraversableLikeé herdada por todas as características que se estendem Map, Ae Reprpode ser herdada de qualquer um deles. O último recebe a preferência, no entanto. Então, seguindo a definição do imutável Mape de todas as características que o conectam TraversableLike, temos:
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
Se você passar os parâmetros de tipo de Map[Int, String]toda a cadeia, descobrimos que os tipos passados para TraversableLikee, portanto, usados por mapsão:
A = (Int,String)
Repr = Map[Int, String]
Voltando ao exemplo, o primeiro mapa está recebendo uma função do tipo ((Int, String)) => (Int, Int)e o segundo mapa está recebendo uma função do tipo ((Int, String)) => String. Eu uso o parêntese duplo para enfatizar que é uma tupla sendo recebida, pois esse é o tipo Aque vimos.
Com essa informação, vamos considerar os outros tipos.
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
Podemos ver que o tipo retornado pelo primeiro mapé Map[Int,Int]e o segundo é Iterable[String]. Olhando para mapa definição de, é fácil ver que esses são os valores de That. Mas de onde eles vêm?
Se olharmos dentro dos objetos complementares das classes envolvidas, vemos algumas declarações implícitas fornecendo-os. No objeto Map:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
E no objeto Iterable, cuja classe é estendida por Map:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
Essas definições fornecem fábricas para parametrizados CanBuildFrom.
Scala escolherá o implícito mais específico disponível. No primeiro caso, foi o primeiro CanBuildFrom. No segundo caso, como o primeiro não correspondeu, ele escolheu o segundo CanBuildFrom.
Voltar à pergunta
Vamos ver o código da definição de pergunta Liste de map(novamente) para ver como os tipos são inferidos:
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
O tipo de List("London", "Paris")é List[String], portanto, os tipos Ae os Reprdefinidos TraversableLikesão:
A = String
Repr = List[String]
O tipo de (x => (x.length, x))é (String) => (Int, String), portanto, o tipo de Bé:
B = (Int, String)
O último tipo desconhecido, Thaté o tipo do resultado de map, e já o temos também:
val map : Map[Int,String] =
Assim,
That = Map[Int, String]
Isso significa que breakOutdeve, necessariamente, retornar um tipo ou subtipo de CanBuildFrom[List[String], (Int, String), Map[Int, String]].
List, mas paramap.