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 mem um objeto ode 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 Cda algo que faz suporte m. Um exemplo simples seria o método mapem String:
"abc".map(_.toInt)
Stringnão suporta o método map, mas StringOpssim, e há uma conversão implícita de Stringpara StringOpsdisponível (consulte a implicit def augmentStringseguir 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 foodeclaraçã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 getIndexpode 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 Stringpara getIndexe 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, getIndexpode 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 Integralclasse 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 sortedon Seqprecisa 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 Optionhá uma conversão implícita para Iterable, portanto, pode-se chamar Iterablemétodos Optionou passar Optionpara 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.flatMapespera a TraversableOnce, o que Optionnão é. O compilador, então, procura Optiono 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 sortedleva 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 Atambém será considerado. Por "escopo implícito", quero dizer que todas essas regras serão aplicadas recursivamente - por exemplo, o objeto complementar de Aserá pesquisado por implícitos, conforme a regra acima.
Observe que isso não significa que o escopo implícito de Aserá 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 Orderingpara 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 sortedespera um Ordering[A](na verdade, ele espera um Ordering[B], onde B >: A). Não existe nada assim Orderinge 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 CanBuildFromtrabalho: 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: