Tipos de implícitos
Implícitos no Scala refere-se a um valor que pode ser passado "automaticamente", por assim dizer, ou a uma conversão de um tipo para outro que é feita automaticamente.
Conversão implícita
Falando brevemente sobre o último tipo, se alguém chama um método m
em um objeto o
de uma classe C
, e essa classe não faz método de apoio m
, então Scala vai olhar para uma conversão implícita C
da algo que faz suporte m
. Um exemplo simples seria o método map
em String
:
"abc".map(_.toInt)
String
não suporta o método map
, mas StringOps
sim, e há uma conversão implícita de String
para StringOps
disponível (consulte a implicit def augmentString
seguir Predef
).
Parâmetros implícitos
O outro tipo de implícito é o parâmetro implícito . Eles são passados para chamadas de método como qualquer outro parâmetro, mas o compilador tenta preenchê-las automaticamente. Se não puder, irá reclamar. Um pode passar esses parâmetros explicitamente, que é como se usa breakOut
, por exemplo (ver pergunta sobre breakOut
, em um dia você está sentindo-se de um desafio).
Nesse caso, é preciso declarar a necessidade de um implícito, como a foo
declaração do método:
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
Exibir limites
Há uma situação em que um implícito é uma conversão implícita e um parâmetro implícito. Por exemplo:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
getIndex("abc", 'a')
O método getIndex
pode receber qualquer objeto, desde que haja uma conversão implícita disponível de sua classe para Seq[T]
. Por causa disso, posso passar um String
para getIndex
e ele funcionará.
Nos bastidores, o compilador muda seq.IndexOf(value)
para conv(seq).indexOf(value)
.
Isso é tão útil que existe açúcar sintático para escrevê-los. Usando este açúcar sintático, getIndex
pode ser definido assim:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
Esse açúcar sintático é descrito como um limite de vista , semelhante a um limite superior ( CC <: Seq[Int]
) ou um limite inferior ( T >: Null
).
Limites de contexto
Outro padrão comum em parâmetros implícitos é o padrão de classe de tipo . Esse padrão permite o fornecimento de interfaces comuns para classes que não as declararam. Pode servir como um padrão de ponte - obtendo uma separação de preocupações - e como um padrão de adaptador.
A Integral
classe que você mencionou é um exemplo clássico de padrão de classe de tipo. Outro exemplo na biblioteca padrão do Scala é Ordering
. Há uma biblioteca que faz uso intenso desse padrão, chamado Scalaz.
Este é um exemplo de seu uso:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Também há açúcar sintático para ele, chamado de contexto vinculado , que se torna menos útil pela necessidade de se referir ao implícito. Uma conversão direta desse método se parece com isso:
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Os limites de contexto são mais úteis quando você só precisa passá- los para outros métodos que os utilizam. Por exemplo, o método sorted
on Seq
precisa de um implícito Ordering
. Para criar um método reverseSort
, pode-se escrever:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
Como Ordering[T]
foi implicitamente passado para reverseSort
, ele pode transmiti-lo implicitamente para sorted
.
De onde vêm os implícitos?
Quando o compilador vê a necessidade de um implícito, seja porque você está chamando um método que não existe na classe do objeto ou porque você está chamando um método que requer um parâmetro implícito, ele procurará um implícito que atenda à necessidade .
Essa pesquisa obedece a certas regras que definem quais implícitos são visíveis e quais não são. A tabela a seguir, que mostra onde o compilador procurará implícitos, foi tirada de uma excelente apresentação sobre implícitos por Josh Suereth, que eu recomendo vivamente a quem quiser aprimorar seus conhecimentos sobre o Scala. Foi complementado desde então com comentários e atualizações.
Os implícitos disponíveis no número 1 abaixo têm precedência sobre os sob o número 2. Além disso, se houver vários argumentos elegíveis que correspondam ao tipo de parâmetro implícito, um mais específico será escolhido usando as regras da resolução de sobrecarga estática (consulte Scala Especificação §6.26.3). Informações mais detalhadas podem ser encontradas em uma pergunta à qual vinculo no final desta resposta.
- Primeiro olhar no escopo atual
- Implícitos definidos no escopo atual
- Importações explícitas
- importações curinga
Mesmo escopo em outros arquivos
- Agora observe os tipos associados em
- Objetos complementares de um tipo
- Escopo implícito do tipo de argumento (2.9.1)
- Escopo implícito dos argumentos de tipo (2.8.0)
- Objetos externos para tipos aninhados
- Outras dimensões
Vamos dar alguns exemplos para eles:
Implícitos definidos no escopo atual
implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope
Importações explícitas
import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM") // implicit conversion from Java Map to Scala Map
Importações curinga
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Mesmo escopo em outros arquivos
Edit : Parece que isso não tem uma precedência diferente. Se você tem algum exemplo que demonstra uma distinção de precedência, faça um comentário. Caso contrário, não confie neste.
É como o primeiro exemplo, mas assumindo que a definição implícita esteja em um arquivo diferente do seu uso. Veja também como os objetos de pacote podem ser usados para trazer implícitos.
Objetos complementares de um tipo
Existem dois companheiros de objeto de nota aqui. Primeiro, o objeto complementar do tipo "origem" é examinado. Por exemplo, dentro do objeto Option
há uma conversão implícita para Iterable
, portanto, pode-se chamar Iterable
métodos Option
ou passar Option
para algo que espera um Iterable
. Por exemplo:
for {
x <- List(1, 2, 3)
y <- Some('x')
} yield (x, y)
Essa expressão é traduzida pelo compilador para
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
No entanto, List.flatMap
espera a TraversableOnce
, o que Option
não é. O compilador, então, procura Option
o objeto complementar do objeto e encontra a conversão para Iterable
, que é a TraversableOnce
, tornando essa expressão correta.
Segundo, o objeto complementar do tipo esperado:
List(1, 2, 3).sorted
O método sorted
leva um implícito Ordering
. Nesse caso, ele olha dentro do objeto Ordering
, companheiro da classe Ordering
, e encontra um implícito Ordering[Int]
.
Observe que os objetos complementares das super classes também são analisados. Por exemplo:
class A(val n: Int)
object A {
implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b // s == "A: 2"
Foi assim que Scala encontrou o implícito Numeric[Int]
e, Numeric[Long]
na sua pergunta, a propósito, como eles são encontrados no interior Numeric
, não Integral
.
Escopo implícito do tipo de um argumento
Se você tiver um método com um tipo de argumento A
, o escopo implícito do tipo A
também será considerado. Por "escopo implícito", quero dizer que todas essas regras serão aplicadas recursivamente - por exemplo, o objeto complementar de A
será pesquisado por implícitos, conforme a regra acima.
Observe que isso não significa que o escopo implícito de A
será pesquisado para conversões desse parâmetro, mas de toda a expressão. Por exemplo:
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
}
object A {
implicit def fromInt(n: Int) = new A(n)
}
// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)
Está disponível desde o Scala 2.9.1.
Escopo implícito dos argumentos de tipo
Isso é necessário para que o padrão de classe de tipo realmente funcione. Considere Ordering
, por exemplo: ele vem com alguns implícitos em seu objeto complementar, mas você não pode adicionar coisas a ele. Então, como você pode criar um Ordering
para sua própria classe que é encontrada automaticamente?
Vamos começar com a implementação:
class A(val n: Int)
object A {
implicit val ord = new Ordering[A] {
def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
}
}
Portanto, considere o que acontece quando você liga
List(new A(5), new A(2)).sorted
Como vimos, o método sorted
espera um Ordering[A]
(na verdade, ele espera um Ordering[B]
, onde B >: A
). Não existe nada assim Ordering
e não existe um tipo de "fonte" no qual procurar. Obviamente, está encontrando-o por dentro A
, que é um argumento de tipoOrdering
.
Também é assim que vários métodos de coleção que esperam o CanBuildFrom
trabalho: os implícitos são encontrados nos objetos complementares nos parâmetros de tipo de CanBuildFrom
.
Nota : Ordering
é definido como trait Ordering[T]
, onde T
é um parâmetro de tipo. Anteriormente, eu disse que o Scala olhou dentro dos parâmetros de tipo, o que não faz muito sentido. O implícito procurado acima é Ordering[A]
, onde A
é um tipo real, não um parâmetro de tipo: é um argumento de tipo para Ordering
. Consulte a seção 7.2 da especificação Scala.
Está disponível desde o Scala 2.8.0.
Objetos externos para tipos aninhados
Na verdade, não vi exemplos disso. Ficaria grato se alguém pudesse compartilhar um. O princípio é simples:
class A(val n: Int) {
class B(val m: Int) { require(m < n) }
}
object A {
implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b // s == "B: 3"
Outras dimensões
Tenho certeza de que isso foi uma piada, mas essa resposta pode não estar atualizada. Portanto, não tome essa pergunta como árbitro final do que está acontecendo e, se você notou que ela ficou desatualizada, informe-me para que eu possa consertá-la.
EDITAR
Questões de interesse relacionadas:
Eu queria descobrir a precedência da resolução implícita de parâmetros, não apenas onde ela procura, então escrevi uma postagem no blog revisitando implícitos sem imposto de importação (e a precedência implícita de parâmetros novamente após alguns comentários).
Aqui está a lista:
Se em qualquer um dos estágios encontrarmos mais de uma regra implícita, a sobrecarga estática será usada para resolvê-la.
fonte