Arten von Implikationen
Implizite in Scala beziehen sich entweder auf einen Wert, der sozusagen "automatisch" übergeben werden kann, oder auf eine Konvertierung von einem Typ in einen anderen, die automatisch vorgenommen wird.
Implizite Konvertierung
Wenn man kurz über den letzteren Typ spricht und eine Methode m
für ein Objekt o
einer Klasse aufruft C
und diese Klasse keine Methode unterstützt m
, sucht Scala nach einer impliziten Konvertierung von C
zu etwas, das unterstützt m
. Ein einfaches Beispiel wäre die Methode map
auf String
:
"abc".map(_.toInt)
String
unterstützt die Methode nicht map
, StringOps
tut es aber und es gibt eine implizite Konvertierung von String
zu StringOps
verfügbar (siehe implicit def augmentString
weiter Predef
).
Implizite Parameter
Die andere Art von implizit ist der implizite Parameter . Diese werden wie alle anderen Parameter an Methodenaufrufe übergeben, aber der Compiler versucht, sie automatisch auszufüllen. Wenn es nicht kann, wird es sich beschweren. Man kann diese Parameter explizit übergeben, wie man sie breakOut
beispielsweise verwendet (siehe Frage zu breakOut
, an einem Tag, an dem Sie sich einer Herausforderung stellen).
In diesem Fall muss die Notwendigkeit eines Impliziten wie der foo
Methodendeklaration deklariert werden :
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
Grenzen anzeigen
Es gibt eine Situation, in der ein Impliziter sowohl eine implizite Konvertierung als auch ein impliziter Parameter ist. Zum Beispiel:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
getIndex("abc", 'a')
Die Methode getIndex
kann jedes Objekt empfangen, solange eine implizite Konvertierung von ihrer Klasse in verfügbar ist Seq[T]
. Aus diesem Grund kann ich ein String
an übergeben getIndex
, und es wird funktionieren.
Hinter den Kulissen wechselt der Compiler seq.IndexOf(value)
zu conv(seq).indexOf(value)
.
Dies ist so nützlich, dass es syntaktischen Zucker gibt, um sie zu schreiben. Mit diesem syntaktischen Zucker getIndex
kann wie folgt definiert werden:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
Dieser syntaktische Zucker wird als Ansichtsgrenze beschrieben , ähnlich einer Obergrenze ( CC <: Seq[Int]
) oder einer Untergrenze ( T >: Null
).
Kontextgrenzen
Ein weiteres häufiges Muster in impliziten Parametern ist das Typklassenmuster . Dieses Muster ermöglicht die Bereitstellung gemeinsamer Schnittstellen für Klassen, die diese nicht deklariert haben. Es kann sowohl als Brückenmuster - zur Trennung von Bedenken - als auch als Adaptermuster dienen.
Die von Integral
Ihnen erwähnte Klasse ist ein klassisches Beispiel für ein Muster einer Typklasse. Ein weiteres Beispiel für die Standardbibliothek von Scala ist Ordering
. Es gibt eine Bibliothek namens Scalaz, die dieses Muster stark nutzt.
Dies ist ein Beispiel für seine Verwendung:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Es gibt auch syntaktischen Zucker dafür, der als kontextgebunden bezeichnet wird , was durch die Notwendigkeit, auf das Implizite zu verweisen, weniger nützlich wird. Eine direkte Konvertierung dieser Methode sieht folgendermaßen aus:
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)(_ + _)
}
Context Grenzen sind nützlich , wenn Sie nur brauchen , um passieren sie zu anderen Methoden , die sie verwenden. Zum Beispiel benötigt die Methode sorted
on Seq
ein implizites Ordering
. Um eine Methode zu erstellen reverseSort
, könnte man schreiben:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
Da Ordering[T]
implizit an übergeben wurde reverseSort
, kann es dann implizit an übergeben werden sorted
.
Woher kommen Implizite?
Wenn der Compiler die Notwendigkeit eines Implizits erkennt, entweder weil Sie eine Methode aufrufen, die in der Klasse des Objekts nicht vorhanden ist, oder weil Sie eine Methode aufrufen, für die ein impliziter Parameter erforderlich ist, sucht er nach einem Implizit, das dem Bedarf entspricht .
Diese Suche folgt bestimmten Regeln, die definieren, welche Implikate sichtbar sind und welche nicht. Die folgende Tabelle, die zeigt, wo der Compiler nach Implikits suchen wird, stammt aus einer hervorragenden Präsentation über Implikits von Josh Suereth, die ich jedem wärmstens empfehlen kann, der sein Scala-Wissen verbessern möchte. Es wurde seitdem durch Feedback und Updates ergänzt.
Die unter Nummer 1 unten verfügbaren Implizite haben Vorrang vor denen unter Nummer 2. Abgesehen davon, wenn mehrere geeignete Argumente vorhanden sind, die dem Typ des impliziten Parameters entsprechen, wird nach den Regeln der statischen Überladungsauflösung ein spezifischstes ausgewählt (siehe Scala) Spezifikation §6.26.3). Nähere Informationen finden Sie in einer Frage, auf die ich am Ende dieser Antwort verweise.
- Schauen Sie sich zuerst den aktuellen Umfang an
- Im aktuellen Geltungsbereich definierte Implikationen
- Explizite Importe
- Platzhalterimporte
Gleicher Bereich in anderen Dateien
- Schauen Sie sich nun die zugehörigen Typen in an
- Begleitobjekte eines Typs
- Impliziter Umfang des Typs eines Arguments (2.9.1)
- Impliziter Umfang der Typargumente (2.8.0)
- Äußere Objekte für verschachtelte Typen
- Andere Abmessungen
Geben wir ihnen einige Beispiele:
Im aktuellen Geltungsbereich definierte Implikationen
implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope
Explizite Importe
import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM") // implicit conversion from Java Map to Scala Map
Platzhalterimporte
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)(_ + _)
}
Gleicher Bereich in anderen Dateien
Bearbeiten : Es scheint, dass dies keine andere Priorität hat. Wenn Sie ein Beispiel haben, das eine Prioritätsunterscheidung zeigt, geben Sie bitte einen Kommentar ab. Andernfalls verlassen Sie sich nicht auf diesen.
Dies ist wie im ersten Beispiel, jedoch unter der Annahme, dass sich die implizite Definition in einer anderen Datei befindet als ihre Verwendung. Siehe auch, wie Paketobjekte verwendet werden können, um Implizite einzufügen.
Begleitobjekte eines Typs
Hier gibt es zwei bemerkenswerte Objektbegleiter. Zunächst wird der Objektbegleiter vom Typ "Quelle" untersucht. Zum Beispiel gibt es innerhalb des Objekts Option
eine implizite Konvertierung in Iterable
, sodass man Iterable
Methoden aufrufen Option
oder Option
an etwas übergeben kann, das eine erwartet Iterable
. Zum Beispiel:
for {
x <- List(1, 2, 3)
y <- Some('x')
} yield (x, y)
Dieser Ausdruck wird vom Compiler in übersetzt
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
Allerdings List.flatMap
erwartet ein TraversableOnce
, was Option
nicht ist. Der Compiler schaut dann in Option
den Objektbegleiter und findet die Konvertierung in Iterable
a TraversableOnce
, wodurch dieser Ausdruck korrekt wird.
Zweitens das Begleitobjekt des erwarteten Typs:
List(1, 2, 3).sorted
Die Methode ist sorted
implizit Ordering
. In diesem Fall schaut es in das Objekt Ordering
, den Begleiter der Klasse Ordering
, und findet dort ein implizites Objekt Ordering[Int]
.
Beachten Sie, dass auch Begleitobjekte von Superklassen untersucht werden. Zum Beispiel:
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"
So fand Scala das Implizite Numeric[Int]
und Numeric[Long]
in Ihrer Frage übrigens, wie sie sich darin befinden Numeric
, nicht Integral
.
Impliziter Umfang des Typs eines Arguments
Wenn Sie eine Methode mit einem Argumenttyp haben A
, A
wird auch der implizite Umfang des Typs berücksichtigt. Mit "impliziter Bereich" meine ich, dass alle diese Regeln rekursiv angewendet werden - zum Beispiel wird das Begleitobjekt von A
gemäß der obigen Regel nach impliziten durchsucht.
Beachten Sie, dass dies nicht bedeutet, dass der implizite Bereich von A
nach Konvertierungen dieses Parameters durchsucht wird, sondern des gesamten Ausdrucks. Zum Beispiel:
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)
Dies ist seit Scala 2.9.1 verfügbar.
Impliziter Umfang der Typargumente
Dies ist erforderlich, damit das Typklassenmuster wirklich funktioniert. Bedenken Ordering
Sie zum Beispiel: Das Begleitobjekt enthält einige Implikationen, aber Sie können nichts hinzufügen. Wie können Sie also eine Ordering
für Ihre eigene Klasse erstellen, die automatisch gefunden wird?
Beginnen wir mit der Implementierung:
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)
}
}
Überlegen Sie also, was passiert, wenn Sie anrufen
List(new A(5), new A(2)).sorted
Wie wir gesehen haben, sorted
erwartet die Methode ein Ordering[A]
(tatsächlich erwartet sie ein Ordering[B]
, wo B >: A
). Es gibt so etwas nicht im Inneren Ordering
und es gibt keinen "Quellentyp", auf den man schauen könnte. Offensichtlich findet es es im Inneren A
, was ein typisches Argument von ist Ordering
.
Auf diese Weise funktionieren auch verschiedene Erfassungsmethoden, die CanBuildFrom
Arbeit erwarten : Die Implikationen werden in Begleitobjekten zu den Typparametern von gefunden CanBuildFrom
.
Hinweis : Ordering
ist definiert als trait Ordering[T]
, wo T
ein Typparameter ist. Zuvor habe ich gesagt, dass Scala sich mit Typparametern befasst, was wenig Sinn macht. Das oben gesuchte implizite ist Ordering[A]
, wo A
ein tatsächlicher Typ ist, kein Typparameter: Es ist ein Typargument für Ordering
. Siehe Abschnitt 7.2 der Scala-Spezifikation.
Dies ist seit Scala 2.8.0 verfügbar.
Äußere Objekte für verschachtelte Typen
Ich habe keine Beispiele dafür gesehen. Ich wäre dankbar, wenn jemand einen teilen könnte. Das Prinzip ist einfach:
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"
Andere Abmessungen
Ich bin mir ziemlich sicher, dass dies ein Witz war, aber diese Antwort ist möglicherweise nicht aktuell. Nehmen Sie diese Frage also nicht als endgültigen Schiedsrichter für das Geschehen. Wenn Sie bemerkt haben, dass sie veraltet ist, informieren Sie mich bitte, damit ich sie beheben kann.
BEARBEITEN
Verwandte Fragen von Interesse:
Ich wollte den Vorrang der impliziten Parameterauflösung herausfinden, nicht nur, wo sie gesucht wird, und schrieb deshalb einen Blog-Beitrag , in dem implizite Vorgaben ohne Importsteuer (und nach einigen Rückmeldungen erneut implizite Vorrang vor Parametern) erneut aufgegriffen wurden.
Hier ist die Liste:
Wenn wir in beiden Phasen mehr als eine implizite statische Überladungsregel finden, wird diese behoben.
quelle