Inferência implícita em Scala

Muitos desenvolvedores iniciante e não tão Scala consideram implícito como um recurso moderadamente útil. O uso é geralmente limitado a passagem ExecutionContext



 para Future



. Outros evitam o implícito e consideram a oportunidade prejudicial.





Um código como este assusta muitas pessoas:





implicit def function(implicit argument: A): B
      
      



Mas acho que esse mecanismo é uma vantagem importante da linguagem, vamos ver por quê.





Resumidamente sobre os implícitos

Em geral, implícito é um mecanismo para preenchimento automático de código durante a compilação:





  • para argumentos implícitos, o valor é automaticamente substituído





  • para conversões implícitas, o valor é automaticamente incluído em uma chamada de método





Não irei me aprofundar neste tópico, a quem interessa assistir a este vídeo do criador da linguagem . Em suma, este mecanismo é usado em vários casos diferentes (e no Scala 3 será dividido em vários recursos separados):





  1. Passando contexto implícito (como ExecutionContext



    )





  2. Conversões implícitas (obsoleto)





  3. Métodos de extensão (açúcar sintático para adicionar métodos a tipos existentes)





  4. Classes de tipo e resolução implícita





Classes de tipo e inferência implícita

Por si mesmas, as classes de tipo não trazem nenhuma novidade ou revolução - elas são simplesmente a implementação de métodos "para" o tipo, e não "no" tipo em si, é como a diferença entre Comparable



 & Ordering



( Comparator



 em Java):





Comparable



  :





class Person(age: Int) extends Comparable[Person] {
  override def compareTo(o: Person): Int = age compareTo o.age
}
def max[T <: Comparable[T]](xs: Iterable[T]): T = xs.reduce[T] {
  case (a, b) if (a compareTo b) < 0 => b
  case (a, _) => a
}
      
      



Ordering



  , :





case class Person(age: Int)

implicit object ByAgeOrdering extends Ordering[Person] {
  override def compare(o1: Person, o2: Person): Int = o1.age compareTo o2.age
}
def max[T: Ordering](xs: Iterable[T]): T = xs.reduce[T] {
  case (a, b) if Ordering[T].lt(a, b) => b
  case (a, _) => a
}
// is syntactic sugar for
def max[T](xs: Iterable[T])(implicit evidence: Ordering[T]): T = ...
      
      



.





. :





implicit val value: A = ???
implicit def definition: B = ???
implicit def conversion(argument: C): D = ???
implicit def function(implicit argument: E): F = ???
      
      



? conversion



, , : value



, definition



 & function



  . : val



 , def



  . – .





– , , .





– , – , :





implicit def pairOrder[A: Ordering, B: Ordering]: Ordering[(A, B)] = {
  case ((a1, b1), (a2, b2)) if Ordering[A].equiv(a1, a2) => Ordering[B].compare(b1, b2)
  case ((a1,  _), (a2,  _)) => Ordering[A].compare(a1, a2)
}
// again, just syntactic sugar for:
implicit def pairOrder[A, B](implicit a: Ordering[A], b: Ordering[B]): Ordering[(A, B)] = ...
      
      



, :





val values = Seq(
  (Person(30), ("A", "A")),
  (Person(30), ("A", "B")),
  (Person(20), ("A", "C"))
)
max(values) // => (Person(30),(A,B))
      
      



Seq[(Person, (String, String))]



  Ordering



  :





max(values)(
  pairOrder(
    ByAgeOrdering, 
    pairOrder(Ordering.String, Ordering.String)
  )
)
      
      



Portanto, a inferência implícita permite que você descreva as regras gerais de inferência e instrua o compilador a combinar essas regras e obter uma implementação específica da classe de tipo. Adicionar seu próprio tipo ou suas próprias regras não precisa descrever tudo desde o início - o compilador combinará tudo sozinho para obter o objeto desejado.





E o mais importante, se o compilador falhar, você receberá um erro de compilação, não um erro de tempo de execução, e poderá corrigir o problema imediatamente. Embora, é claro, haja uma mosca na pomada na pomada - se o compilador falhou, você não sabe qual elo da cadeia estava faltando - nem sempre é fácil depurar isso.





Esperançosamente, o implícito agora está um pouco mais explícito.








All Articles