Viele Anfänger und nicht so Scala-Entwickler betrachten Implikits als mäßig nützliche Funktion. Die Verwendung beschränkt sich normalerweise auf die Weitergabe ExecutionContext
an Future
. Andere vermeiden das Implizite und betrachten die Gelegenheit als schädlich.
Code wie dieser macht vielen Menschen Angst:
implicit def function(implicit argument: A): B
Aber ich denke, dieser Mechanismus ist ein wichtiger Vorteil der Sprache. Mal sehen, warum.
Kurz über Implizite
Implicits ist im Allgemeinen ein Mechanismus zur automatischen Code-Vervollständigung während der Kompilierung:
Bei impliziten Argumenten wird der Wert automatisch ersetzt
Bei impliziten Konvertierungen wird der Wert automatisch in einen Methodenaufruf eingeschlossen
Ich werde nicht weiter auf dieses Thema eingehen, die daran interessiert sind, dieses Video vom Schöpfer der Sprache zu sehen . Kurz gesagt, dieser eine Mechanismus wird in mehreren verschiedenen Fällen verwendet (und in Scala 3 wird er in mehrere separate Funktionen unterteilt):
Impliziten Kontext übergeben (wie
ExecutionContext
)
Implizite Conversions (veraltet)
Erweiterungsmethoden (syntaktischer Zucker zum Hinzufügen von Methoden zu vorhandenen Typen)
Typklassen und implizite Auflösung
Typklassen und implizite Inferenz
Typ-Klassen bringen für sich genommen keine Neuheit oder Revolution - sie sind einfach die Implementierung von Methoden "für" den Typ und nicht "in" den Typ selbst, es ist wie der Unterschied zwischen Comparable
& Ordering
( Comparator
in 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)
)
)
Mit impliziter Inferenz können Sie also allgemeine Inferenzregeln beschreiben und den Compiler anweisen, diese Regeln miteinander zu kombinieren und eine bestimmte Implementierung der Typklasse zu erhalten. Das Hinzufügen eines eigenen Typs oder Ihrer eigenen Regeln muss nicht alles von Anfang an beschreiben - der Compiler kombiniert alles selbst, um das gewünschte Objekt zu erhalten.
Und vor allem, wenn der Compiler ausfällt, erhalten Sie einen Kompilierungsfehler, keinen Laufzeitfehler, und Sie können das Problem sofort beheben. Obwohl es natürlich eine Fliege in der Salbe in der Salbe gibt - wenn der Compiler versagt hat, wissen Sie nicht, welches Glied in der Kette fehlte - ist es nicht immer einfach, dies zu debuggen.
Hoffentlich ist das Implizite jetzt etwas expliziter.