Muchos desarrolladores principiantes y no tan de Scala toman los implícitos como una característica moderadamente útil. El uso generalmente se limita a pasar ExecutionContext
a Future
. Otros evitan lo implícito y ven la oportunidad como dañina.
Un código como este asusta a mucha gente:
implicit def function(implicit argument: A): B
Pero creo que este mecanismo es una ventaja importante del lenguaje, veamos por qué.
Brevemente sobre implicitos
En general, implicits es un mecanismo para la finalización automática del código durante la compilación:
para argumentos implícitos, el valor se sustituye automáticamente
para las conversiones implícitas, el valor se envuelve automáticamente en una llamada al método
No voy a profundizar en este tema, a quienes les interesa ver este video del creador del lenguaje . En pocas palabras, este mecanismo se usa en varios casos diferentes (y en Scala 3 se dividirá en varias funciones separadas):
Pasando contexto implícito (como
ExecutionContext
)
Conversiones implícitas (obsoletas)
Métodos de extensión (azúcar sintáctico para agregar métodos a tipos existentes)
Clases de tipos y resolución implícita
Clases de tipos e inferencia implícita
Por sí mismas, las clases de tipos no aportan ninguna novedad o revolución: son simplemente la implementación de métodos "para" el tipo, y no "en" el tipo en sí, es como la diferencia entre Comparable
& Ordering
( Comparator
en 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)
)
)
Entonces, la inferencia implícita le permite describir reglas de inferencia generales e instruir al compilador para que combine estas reglas y obtenga una implementación específica de la clase de tipo. Agregar su propio tipo o sus propias reglas no necesita describir todo desde el principio: el compilador combinará todo para obtener el objeto deseado.
Y lo más importante, si el compilador falla, recibirá un error de compilación, no un error de tiempo de ejecución, y podrá solucionar el problema de inmediato. Aunque, por supuesto, hay una mosca en la pomada en la pomada (si el compilador falla, no sabe qué eslabón de la cadena faltaba), no siempre es fácil depurar esto.
Ojalá lo implícito sea ahora un poco más explícito.