Was sind auf einfache Weise Kontext- und Sichtgrenzen und was ist der Unterschied zwischen ihnen?
Einige leicht zu verfolgende Beispiele wären auch großartig!
Ich dachte, dies wurde bereits gestellt, aber wenn ja, ist die Frage in der "verwandten" Leiste nicht ersichtlich. Hier ist es also:
Eine Ansichtsbindung war ein in Scala eingeführter Mechanismus, um die Verwendung eines Typs zu ermöglichen, A
als wäre es ein Typ B
. Die typische Syntax lautet wie folgt:
def f[A <% B](a: A) = a.bMethod
Mit anderen Worten, A
sollte eine implizite Konvertierung in B
verfügbar sein, damit man B
Methoden für ein Objekt vom Typ aufrufen kann A
. Die häufigste Verwendung von Ansichtsgrenzen in der Standardbibliothek (jedenfalls vor Scala 2.8.0) ist Ordered
wie folgt :
def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
Da man A
in eine konvertieren Ordered[A]
kann und weil Ordered[A]
die Methode definiert ist <(other: A): Boolean
, kann ich den Ausdruck verwenden a < b
.
Bitte beachten Sie, dass Ansichtsgrenzen veraltet sind. Sie sollten sie vermeiden.
Kontextgrenzen wurden in Scala 2.8.0 eingeführt und werden normalerweise mit dem sogenannten Typklassenmuster verwendet , einem Codemuster, das die von Haskell-Typklassen bereitgestellten Funktionen emuliert, wenn auch ausführlicher.
Während eine Ansichtsbindung mit einfachen Typen verwendet werden kann (z. B. A <% String
), erfordert eine Kontextbindung einen parametrisierten Typ , wie Ordered[A]
oben, jedoch nicht String
.
Eine Kontextbindung beschreibt einen impliziten Wert anstelle der impliziten Konvertierung der Ansichtsgrenze . Es wird verwendet, um zu deklarieren, dass für einen Typ A
ein impliziter Wert des Typs B[A]
verfügbar ist. Die Syntax lautet wie folgt:
def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
Dies ist verwirrender als die gebundene Ansicht, da nicht sofort klar ist, wie sie verwendet werden soll. Das häufigste Beispiel für die Verwendung in Scala ist:
def f[A : ClassManifest](n: Int) = new Array[A](n)
Für eine Array
Initialisierung eines parametrisierten Typs muss ClassManifest
aus arkanen Gründen, die mit dem Löschen des Typs und der Nichtlöschbarkeit von Arrays zusammenhängen , ein verfügbar sein.
Ein weiteres sehr häufiges Beispiel in der Bibliothek ist etwas komplexer:
def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
Hier implicitly
wird verwendet, um den gewünschten impliziten Wert vom Typ abzurufen Ordering[A]
, dessen Klasse die Methode definiert compare(a: A, b: A): Int
.
Wir werden unten einen anderen Weg sehen, dies zu tun.
Es sollte nicht überraschen, dass sowohl Ansichtsgrenzen als auch Kontextgrenzen aufgrund ihrer Definition mit impliziten Parametern implementiert werden. Eigentlich ist die Syntax, die ich gezeigt habe, syntaktischer Zucker für das, was wirklich passiert. Sehen Sie unten, wie sie Zucker entziehen:
def f[A <% B](a: A) = a.bMethod
def f[A](a: A)(implicit ev: A => B) = a.bMethod
def g[A : B](a: A) = h(a)
def g[A](a: A)(implicit ev: B[A]) = h(a)
Natürlich kann man sie in ihrer vollständigen Syntax schreiben, was besonders für Kontextgrenzen nützlich ist:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
Ansichtsgrenzen werden hauptsächlich verwendet, um das Pimp-My-Library- Muster zu nutzen, mit dem einer vorhandenen Klasse Methoden "hinzugefügt" werden, wenn Sie den ursprünglichen Typ irgendwie zurückgeben möchten. Wenn Sie diesen Typ in keiner Weise zurückgeben müssen, benötigen Sie keine gebundene Ansicht.
Das klassische Beispiel für die Verwendung von Ansichten ist die Handhabung Ordered
. Beachten Sie, dass dies Int
beispielsweise nicht der Fall ist Ordered
, obwohl eine implizite Konvertierung vorliegt. Für das zuvor angegebene Beispiel ist eine Ansicht gebunden, da der nicht konvertierte Typ zurückgegeben wird:
def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b
Dieses Beispiel funktioniert nicht ohne Ansichtsgrenzen. Wenn ich jedoch einen anderen Typ zurückgeben möchte, brauche ich keine gebundene Ansicht mehr:
def f[A](a: Ordered[A], b: A): Boolean = a < b
Die Konvertierung hier (falls erforderlich) erfolgt, bevor ich den Parameter an übergebe f
, f
muss also nichts darüber wissen.
Außerdem Ordered
ist die am weitesten verbreitete Anwendung aus der Bibliothek Handhabung String
und Array
, die Java - Klassen sind, wie sie Scala Sammlungen waren. Zum Beispiel:
def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b
Wenn man dies ohne Sichtgrenzen versuchen String
würde, wäre der Rückgabetyp von a a WrappedString
(Scala 2.8) und ähnlich für Array
.
Das gleiche passiert auch, wenn der Typ nur als Typparameter des Rückgabetyps verwendet wird:
def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted
Kontextgrenzen werden hauptsächlich in dem verwendet, was als bekannt geworden ist typeclass Muster , als Verweis auf Haskells Typklassen. Grundsätzlich implementiert dieses Muster eine Alternative zur Vererbung, indem Funktionen über eine Art implizites Adaptermuster verfügbar gemacht werden.
Das klassische Beispiel ist Scala 2.8 Ordering
, das in der Ordered
gesamten Scala-Bibliothek ersetzt wurde. Die Verwendung ist:
def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b
Obwohl Sie das normalerweise so sehen werden:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
import ord.mkOrderingOps
if (a < b) a else b
}
Die einige implizite Konvertierungen nutzen Ordering
, die den traditionellen Operatorstil ermöglichen. Ein weiteres Beispiel in Scala 2.8 ist Numeric
:
def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)
Ein komplexeres Beispiel ist die Verwendung der neuen Sammlung von CanBuildFrom
, aber darauf gibt es bereits eine sehr lange Antwort, daher werde ich sie hier vermeiden. Und wie bereits erwähnt, gibt es dieClassManifest
Verwendung, die erforderlich ist, um neue Arrays ohne konkrete Typen zu initialisieren.
Der mit dem Muster der Typklasse gebundene Kontext wird viel eher von Ihren eigenen Klassen verwendet, da sie die Trennung von Bedenken ermöglichen, während Ansichtsgrenzen in Ihrem eigenen Code durch gutes Design vermieden werden können (er wird hauptsächlich verwendet, um das Design eines anderen zu umgehen ).
Obwohl dies schon lange möglich war, hat die Verwendung von Kontextgrenzen im Jahr 2010 stark zugenommen und ist jetzt bis zu einem gewissen Grad in den meisten der wichtigsten Bibliotheken und Frameworks von Scala zu finden. Das extremste Beispiel für seine Verwendung ist jedoch die Scalaz-Bibliothek, die Scala einen Großteil der Kraft von Haskell verleiht. Ich empfehle, sich über Muster von Typklassen zu informieren, um mehr über die Verwendungsmöglichkeiten zu erfahren.
BEARBEITEN
Verwandte Fragen von Interesse: