No Scala 2.8 , há um objeto em 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()
}
Foi-me dito que isso resulta em:
> 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)
O que está acontecendo aqui? Por que está breakOut
sendo chamado como argumento para o meu List
?
scala
scala-2.8
scala-collections
oxbow_lakes
fonte
fonte
List
, mas paramap
.Respostas:
A resposta é encontrada na definição de
map
: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 umMap
resultado. A maneira mais óbvia de fazer isso produziria umaList[(Int, String)]
coleção intermediária e depois a converteria.Dado que
map
usa aBuilder
para produzir a coleção resultante, não seria possível pular o intermediárioList
e coletar os resultados diretamente em umaMap
? Evidentemente, é sim. Para isso, no entanto, é preciso passar por um adequadoCanBuildFrom
paramap
, e que é exatamente o quebreakOut
faz.Vejamos, então, a definição de
breakOut
:Observe que
breakOut
está parametrizado e retorna uma instância deCanBuildFrom
. Por acaso, os tiposFrom
,T
eTo
já foram inferidos, porque sabemos que issomap
é esperadoCanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Portanto:Para concluir, vamos examinar o implícito recebido por
breakOut
si só. É do tipoCanBuildFrom[Nothing,T,To]
. Já conhecemos todos esses tipos, para podermos determinar que precisamos de um tipo implícitoCanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Mas existe essa definição?Vamos dar uma olhada na
CanBuildFrom
definição de:Então,
CanBuildFrom
é contra-variante em seu primeiro parâmetro de tipo. ComoNothing
é uma classe inferior (ou seja, é uma subclasse de tudo), isso significa que qualquer classe pode ser usada no lugar deNothing
.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, umList
construtor retornará aList
, umMap
construtor retornará aMap
e assim por diante. A implementação domap
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 escrevesseMap('a' -> 1).map(_.swap)
, gostaria de meMap(1 -> 'a')
vingar. Por outro lado, aMap('a' -> 1).map(_._1)
não pode retornar aMap
(retorna umaIterable
).A mágica de produzir o melhor possível a
Builder
partir dos tipos conhecidos da expressão é realizada por esseCanBuildFrom
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 aList
. Eu voltareiList
mais tarde. Por enquanto, considere estas duas expressões:O primeiro retorna ae
Map
o segundo retorna aIterable
. A mágica de devolver uma coleção apropriada é o trabalho deCanBuildFrom
. Vamos considerar a definição demap
novamente para entendê-la.O método
map
é herdado deTraversableLike
. É parametrizado emB
eThat
, e utiliza os parâmetros de tipoA
eRepr
, que parametrizam a classe. Vamos ver as duas definições juntas:A classe
TraversableLike
é definida como:Para entender de onde
A
e de ondeRepr
vem, vamos considerar a definição emMap
si:Porque
TraversableLike
é herdada por todas as características que se estendemMap
,A
eRepr
pode ser herdada de qualquer um deles. O último recebe a preferência, no entanto. Então, seguindo a definição do imutávelMap
e de todas as características que o conectamTraversableLike
, temos:Se você passar os parâmetros de tipo de
Map[Int, String]
toda a cadeia, descobrimos que os tipos passados paraTraversableLike
e, portanto, usados pormap
são: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 tipoA
que vimos.Com essa informação, vamos considerar os outros tipos.
Podemos ver que o tipo retornado pelo primeiro
map
éMap[Int,Int]
e o segundo éIterable[String]
. Olhando paramap
a definição de, é fácil ver que esses são os valores deThat
. 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
:E no objeto
Iterable
, cuja classe é estendida porMap
: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 segundoCanBuildFrom
.Voltar à pergunta
Vamos ver o código da definição de pergunta
List
e demap
(novamente) para ver como os tipos são inferidos:O tipo de
List("London", "Paris")
éList[String]
, portanto, os tiposA
e osRepr
definidosTraversableLike
são:O tipo de
(x => (x.length, x))
é(String) => (Int, String)
, portanto, o tipo deB
é:O último tipo desconhecido,
That
é o tipo do resultado demap
, e já o temos também:Assim,
Isso significa que
breakOut
deve, necessariamente, retornar um tipo ou subtipo deCanBuildFrom[List[String], (Int, String), Map[Int, String]]
.fonte
Eu gostaria de aproveitar a resposta de Daniel. Foi muito completo, mas, como observado nos comentários, não explica o que a fuga faz.
Retirado de Re: Support for Builders explícito (23-10-2009), eis o que eu acredito que a fuga faz:
Ele fornece ao compilador uma sugestão sobre qual Construtor escolher implicitamente (essencialmente, permite ao compilador escolher qual fábrica ele se encaixa melhor na situação).
Por exemplo, veja o seguinte:
Você pode ver que o tipo de retorno é escolhido implicitamente pelo compilador para corresponder melhor ao tipo esperado. Dependendo de como você declara a variável de recebimento, você obtém resultados diferentes.
A seguir, seria uma maneira equivalente de especificar um construtor. Observe que, neste caso, o compilador inferirá o tipo esperado com base no tipo do construtor:
fonte
breakOut
"? Estou pensando que algo comoconvert
oubuildADifferentTypeOfCollection
(mas mais curto) pode ter sido mais fácil de lembrar.A resposta de Daniel Sobral é ótima e deve ser lida em conjunto com a Architecture of Scala Collections (capítulo 25 de Programação em Scala).
Eu só queria explicar por que é chamado
breakOut
:Por que é chamado
breakOut
?Porque queremos sair de um tipo para outro :
Quebrar de que tipo em que tipo? Vamos ver a
map
funçãoSeq
como um exemplo:Se quisermos criar um mapa diretamente do mapeamento sobre os elementos de uma sequência, como:
O compilador reclamaria:
O motivo é que o Seq sabe apenas como construir outro Seq (ou seja, há uma
CanBuildFrom[Seq[_], B, Seq[B]]
fábrica implícita de construtores disponível, mas NÃO existe uma fábrica de construtores da Seq para o Mapa).Para compilar, precisamos de alguma forma
breakOut
do requisito de tipo e poder construir um construtor que produz um Mapa para amap
função usar.Como Daniel explicou, breakOut tem a seguinte assinatura:
Nothing
é uma subclasse de todas as classes, portanto, qualquer fábrica de construtores pode ser substituída no lugar deimplicit b: CanBuildFrom[Nothing, T, To]
. Se usamos a função breakOut para fornecer o parâmetro implícito:Ele seria compilado, porque
breakOut
é capaz de fornecer o tipo necessário deCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
, enquanto o compilador é capaz de encontrar uma fábrica implícita de construtores do tipoCanBuildFrom[Map[_, _], (A, B), Map[A, B]]
, no lugar deCanBuildFrom[Nothing, T, To]
, para breakOut usar para criar o construtor real.Observe que
CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
é definido no mapa e simplesmente inicia umMapBuilder
que usa um mapa subjacente.Espero que isso esclareça as coisas.
fonte
Um exemplo simples para entender o que
breakOut
faz:fonte
val seq:Seq[Int] = set.map(_ % 2).toVector
não fornecerá os valores repetidos, pois oSet
foi preservado para omap
.set.map(_ % 2)
cria umSet(1, 0)
primeiro, que é convertido em aVector(1, 0)
.