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 Map
resultado. A maneira mais óbvia de fazer isso produziria uma List[(Int, String)]
coleção intermediária e depois a converteria.
Dado que map
usa a Builder
para produzir a coleção resultante, não seria possível pular o intermediário List
e coletar os resultados diretamente em uma Map
? Evidentemente, é sim. Para isso, no entanto, é preciso passar por um adequado CanBuildFrom
para map
, e que é exatamente o que breakOut
faz.
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 breakOut
está parametrizado e retorna uma instância de CanBuildFrom
. Por acaso, os tipos From
, T
e To
já 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 breakOut
si 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 CanBuildFrom
definiçã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 map
transformar 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 List
construtor retornará a List
, um Map
construtor retornará a Map
e assim por diante. A implementação do map
método não precisa se preocupar com o tipo de resultado: o construtor cuida dele.
Por outro lado, isso significa que map
precisa 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 Builder
partir dos tipos conhecidos da expressão é realizada por esse CanBuildFrom
implícito.
Sobre CanBuildFrom
Para explicar melhor o que está acontecendo, darei um exemplo em que a coleção que está sendo mapeada é um em Map
vez de a List
. Eu voltarei List
mais 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 Map
o segundo retorna a Iterable
. A mágica de devolver uma coleção apropriada é o trabalho de CanBuildFrom
. Vamos considerar a definição de map
novamente para entendê-la.
O método map
é herdado de TraversableLike
. É parametrizado em B
e That
, e utiliza os parâmetros de tipo A
e 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 A
e de onde Repr
vem, vamos considerar a definição em Map
si:
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
, A
e Repr
pode ser herdada de qualquer um deles. O último recebe a preferência, no entanto. Então, seguindo a definição do imutável Map
e 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 TraversableLike
e, portanto, usados por map
sã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 A
que 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 map
a 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 List
e 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 A
e os Repr
definidos TraversableLike
sã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 breakOut
deve, necessariamente, retornar um tipo ou subtipo de CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.
List
, mas paramap
.