Was sind Scala-Kontext- und Ansichtsgrenzen?

267

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!

chrsan
quelle

Antworten:

477

Ich dachte, dies wurde bereits gestellt, aber wenn ja, ist die Frage in der "verwandten" Leiste nicht ersichtlich. Hier ist es also:

Was ist eine Ansicht gebunden?

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, Asollte eine implizite Konvertierung in Bverfügbar sein, damit man BMethoden 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 Orderedwie folgt :

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b

Da man Ain 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.

Was ist ein Kontext gebunden?

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 Aein 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 ArrayInitialisierung eines parametrisierten Typs muss ClassManifestaus 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 implicitlywird 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.

Wie werden Ansichts- und Kontextgrenzen implementiert?

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)

Wofür werden Ansichtsgrenzen verwendet?

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 Intbeispielsweise 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, fmuss also nichts darüber wissen.

Außerdem Ordered ist die am weitesten verbreitete Anwendung aus der Bibliothek Handhabung Stringund 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 Stringwü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

Wofür werden Kontextgrenzen verwendet?

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 Orderedgesamten 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:

Daniel C. Sobral
quelle
9
Vielen Dank. Ich weiß, dass dies bereits beantwortet wurde, und vielleicht habe ich damals nicht sorgfältig genug gelesen, aber Ihre Erklärung hier ist die klarste, die ich je gesehen habe. Also nochmals vielen Dank.
Chrsan
3
@chrsan Ich habe zwei weitere Abschnitte hinzugefügt, in denen detaillierter beschrieben wird, wo jeweils einer verwendet wird.
Daniel C. Sobral
2
Ich denke, das ist eine ausgezeichnete Erklärung. Ich möchte dies für meinen deutschen Blog (dgronau.wordpress.com) übersetzen, wenn es Ihnen recht ist.
Landei
3
Dies ist bei weitem die beste und umfassendste Erklärung für dieses Thema, die ich bisher gefunden habe. Danke vielmals!
fotNelton
2
Sooo, wann kommt dein Scala-Buch heraus und wo kann ich es kaufen :)
wfbarksdale