Was ist ein "Kontext gebunden" in Scala?

115

Eine der neuen Funktionen von Scala 2.8 sind Kontextgrenzen. Was ist ein Kontext gebunden und wo ist er nützlich?

Natürlich habe ich zuerst gesucht (und zum Beispiel dies gefunden ), aber ich konnte keine wirklich klaren und detaillierten Informationen finden.

Jesper
quelle
8
Schauen Sie sich
Arjan Blokzijl
2
Diese ausgezeichnete Antwort vergleicht / kontrastiert Kontextgrenzen
Aaron Novstrup
Dies ist eine sehr schöne Antwort stackoverflow.com/a/25250693/1586965
samthebest

Antworten:

107

Haben Sie diesen Artikel gefunden ? Es behandelt die neue kontextgebundene Funktion im Kontext von Array-Verbesserungen.

Im Allgemeinen hat ein Typparameter mit einer Kontextbindung die Form [T: Bound]; Es wird Tzusammen mit einem impliziten Parameter vom Typ zu einem einfachen Typparameter erweitert Bound[T].

Betrachten Sie die Methode, tabulatedie aus den Ergebnissen der Anwendung einer bestimmten Funktion f auf einen Zahlenbereich von 0 bis zu einer bestimmten Länge ein Array bildet. Bis zu Scala 2.7 kann die Tabelle wie folgt geschrieben werden:

def tabulate[T](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

In Scala 2.8 ist dies nicht mehr möglich, da Laufzeitinformationen erforderlich sind, um die richtige Darstellung von zu erstellen Array[T]. Diese Informationen müssen bereitgestellt werden, indem ein ClassManifest[T]als impliziter Parameter an die Methode übergeben wird:

def tabulate[T](len: Int, f: Int => T)(implicit m: ClassManifest[T]) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

Als Kurzform kann stattdessen eine Kontextbindung für den Typparameter Tverwendet werden, die Folgendes ergibt:

def tabulate[T: ClassManifest](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}
Robert Harvey
quelle
145

Roberts Antwort behandelt die technischen Details von Context Bounds. Ich werde Ihnen meine Interpretation ihrer Bedeutung geben.

In Scala A <% Berfasst a View Bound ( ) das Konzept von 'kann als' gesehen werden (während eine Obergrenze <:das Konzept von 'is a' erfasst). Ein Kontext gebunden ( A : C) sagt 'hat ein' über einen Typ. Sie können die Beispiele zu Manifesten als " That ein Manifest" lesen . Das Beispiel, das Sie mit about Orderedvs verknüpft haben, Orderingzeigt den Unterschied. Eine Methode

def example[T <% Ordered[T]](param: T)

sagt, dass der Parameter als ein gesehen werden kann Ordered. Vergleichen mit

def example[T : Ordering](param: T)

was besagt, dass dem Parameter eine zugeordnet ist Ordering.

In Bezug auf die Verwendung hat es eine Weile gedauert, bis Konventionen festgelegt wurden, aber Kontextgrenzen werden gegenüber Ansichtsgrenzen bevorzugt ( Ansichtsgrenzen sind jetzt veraltet ). Ein Vorschlag ist, dass eine Kontextbindung bevorzugt wird, wenn Sie eine implizite Definition von einem Bereich in einen anderen übertragen müssen, ohne direkt darauf verweisen zu müssen (dies ist sicherlich der Fall, wenn ClassManifestein Array erstellt wird).

Eine andere Art, über Sicht- und Kontextgrenzen nachzudenken, besteht darin, dass beim ersten Mal implizite Konvertierungen aus dem Bereich des Anrufers übertragen werden. Der zweite überträgt implizite Objekte aus dem Bereich des Aufrufers.

Ben Lings
quelle
2
"hat ein" statt "ist ein" oder "gesehen als" war die Schlüsselerkenntnis für mich - in keiner anderen Erklärung gesehen. Eine einfache englische Version der ansonsten leicht kryptischen Operatoren / Funktionen erleichtert das Aufnehmen erheblich - danke!
DNA
1
@ Ben Lings Was meinst du mit ... 'hat ein' über einen Typ ...? Was ist mit einem Typ ?
Jhegedus
1
@jhegedus Hier ist meine Analyse: "Über einen Typ" bedeutet, dass A sich auf einen Typ bezieht. Der Ausdruck "hat ein" wird im objektorientierten Entwurf häufig verwendet, um Objektbeziehungen zu beschreiben (z. B. Kunde "hat eine" Adresse). Aber hier besteht die Beziehung "hat eine" zwischen Typen, nicht zwischen Objekten. Es ist eine lose Analogie, weil die Beziehung "hat eine" nicht inhärent oder universell ist, wie es im OO-Design ist; Ein Kunde hat immer eine Adresse, aber für den gebundenen Kontext hat ein A nicht immer ein C. Vielmehr gibt der gebundene Kontext an, dass eine Instanz von C [A] implizit bereitgestellt werden muss.
Jbyler
Ich lerne Scala seit einem Monat und dies ist die beste Erklärung, die ich in diesem Monat gesehen habe! Danke @Ben!
Lifu Huang
@ Ben Lings: Danke, nachdem Sie so lange Zeit damit verbracht haben, zu verstehen, was kontextgebunden ist, ist Ihre Antwort sehr hilfreich. [Macht has afür mich mehr Sinn]
Shankar
39

(Dies ist eine Anmerkung in Klammern. Lesen und verstehen Sie zuerst die anderen Antworten.)

Kontextgrenzen verallgemeinern tatsächlich Ansichtsgrenzen.

Angesichts dieses Codes, der mit einer Ansichtsgrenze ausgedrückt wird:

scala> implicit def int2str(i: Int): String = i.toString
int2str: (i: Int)String

scala> def f1[T <% String](t: T) = 0
f1: [T](t: T)(implicit evidence$1: (T) => String)Int

Dies kann auch mit Hilfe eines Typalias ausgedrückt werden, der Funktionen von Typ Fzu Typ darstellt T.

scala> trait To[T] { type From[F] = F => T }           
defined trait To

scala> def f2[T : To[String]#From](t: T) = 0       
f2: [T](t: T)(implicit evidence$1: (T) => java.lang.String)Int

scala> f2(1)
res1: Int = 0

Eine Kontextbindung muss mit einem Typkonstruktor verwendet werden * => *. Der Typkonstruktor Function1ist jedoch von Art (*, *) => *. Die Verwendung des Typalias wendet teilweise den zweiten Typparameter auf den Typ an String, wodurch ein Typkonstruktor der richtigen Art zur Verwendung als Kontextbindung erhalten wird.

Es gibt einen Vorschlag, mit dem Sie teilweise angewendete Typen in Scala direkt ausdrücken können, ohne den Typalias innerhalb eines Merkmals zu verwenden. Sie könnten dann schreiben:

def f3[T : [X](X => String)](t: T) = 0 
Retronym
quelle
Könnten Sie die Bedeutung von #From in der Definition von f2 erklären? Ich bin nicht sicher, wo der Typ F gebaut wird (habe ich das richtig gesagt?)
Collin
1
Es wird als Typprojektion bezeichnet und verweist auf ein Typelement Fromdes Typs To[String]. Wir geben kein Typargument an From, daher verweisen wir auf den Typkonstruktor, nicht auf einen Typ. Dieser Typkonstruktor ist von der richtigen Art, um als kontextgebunden - verwendet zu werden * -> *. Dies begrenzt den Typparameter, Tindem ein impliziter Parameter vom Typ erforderlich wird To[String]#From[T]. Erweitern Sie die Typ-Aliase und voila, die Ihnen verbleiben Function1[String, T].
Retronym
sollte das Function1 [T, String] sein?
Ssanj
18

Dies ist eine weitere Anmerkung in Klammern.

Wie Ben hervorhob, stellt eine Kontextbindung eine "has-a" -Einschränkung zwischen einem Typparameter und einer Typklasse dar. Anders ausgedrückt, es stellt eine Einschränkung dar, dass ein impliziter Wert einer bestimmten Typklasse vorhanden ist.

Wenn man einen Kontext verwendet, muss man diesen impliziten Wert oft auftauchen lassen. In Anbetracht der Einschränkung T : Orderingwird beispielsweise häufig die Instanz benötigt Ordering[T], die die Einschränkung erfüllt. Wie hier gezeigt , ist es möglich, mit der implicitlyMethode oder einer etwas hilfreicheren contextMethode auf den impliziten Wert zuzugreifen :

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) = 
   xs zip ys map { t => implicitly[Numeric[T]].times(t._1, t._2) }

oder

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) =
   xs zip ys map { t => context[T]().times(t._1, t._2) }
Aaron Novstrup
quelle