Eine der neuen Funktionen von Scala 2.8 sind Kontextgrenzen. Was ist eine Kontextgrenze und wo ist sie nützlich?
Natürlich habe ich zuerst gesucht (und zum Beispiel gefunden this ), aber ich konnte keine wirklich klaren und detaillierten Informationen finden.
Haben Sie diesen Artikel gefunden? Es behandelt das neue kontextgebundene Feature im Rahmen von Array-Verbesserungen.
Im Allgemeinen hat ein Typparameter mit einem kontextgebundenen die Form [T: Bound]
; Es wird zusammen mit einem impliziten Parameter vom Typ Bound[T]
zu einem einfachen Typparameter T
erweitert.
Betrachten Sie die Methode tabulate
, die 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 tabellarisch 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 Array[T]
Zu erstellen. Diese Informationen müssen durch Übergabe eines ClassManifest[T]
als impliziten Parameter in die Methode ein:
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 ein kontextgebundenes für den Typparameter T
verwendet werden, wobei Folgendes angegeben wird:
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
}
Roberts Antwort deckt die technischen Details von Kontext-Grenzen ab. Ich werde Ihnen meine Interpretation ihrer Bedeutung geben.
In Scala a View Bound (A <% B
) fängt das Konzept von 'kann als' ein (wobei eine obere Schranke <:
erfasst das Konzept von 'is a'). Ein Kontext gebunden (A : C
) sagt 'hat ein' über einen Typ. Sie können die Beispiele zu Manifesten lesen, wenn "T
ein Manifest
hat". Das Beispiel, das Sie mit ungefähr Ordered
vs Ordering
verknüpft haben, veranschaulicht den Unterschied. Eine Methode
def example[T <% Ordered[T]](param: T)
sagt, dass der Parameter als Ordered
angesehen werden kann. Vergleichen mit
def example[T : Ordering](param: T)
was besagt, dass dem Parameter ein Ordering
zugeordnet ist.
In Bezug auf die Verwendung dauerte es eine Weile, bis Konventionen festgelegt wurden, aber Kontextgrenzen werden Ansichtsgrenzen vorgezogen ( 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 für das ClassManifest
, das zum Erstellen eines Arrays verwendet wird).
Eine andere Art, über Sicht- und Kontextgrenzen nachzudenken, besteht darin, dass beim ersten Mal implizite Konvertierungen aus dem Bereich des Aufrufers übertragen werden. Der zweite Befehl überträgt implizite Objekte aus dem Bereich des Aufrufers.
(Dies ist eine Anmerkung in Klammern. Lesen und verstehen Sie zuerst die anderen Antworten.)
Kontextgrenzen verallgemeinern eigentlich Ansichtsgrenzen.
Ausgehend von diesem Code, der mit View Bound 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 könnte auch mit Hilfe eines Typalias ausgedrückt werden, der Funktionen vom Typ F
bis zum Typ T
darstellt.
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 der Art * => *
Verwendet werden. Der Typkonstruktor Function1
Ist jedoch vom Typ (*, *) => *
. Die Verwendung des Typalias wendet teilweise den zweiten Typparameter mit dem Typ String
an, 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 in einem Merkmal zu verwenden. Sie könnten dann schreiben:
def f3[T : [X](X => String)](t: T) = 0
Dies ist eine weitere Anmerkung in Klammern.
Wie Ben wies darauf hin , repräsentiert eine Kontextgrenze eine "has-a" -Einschränkung zwischen einem Typparameter und einer Typklasse. Anders ausgedrückt stellt es eine Einschränkung dar, dass ein impliziter Wert einer bestimmten Typklasse vorhanden ist.
Wenn man eine Kontextbindung verwendet, muss man diesen impliziten Wert oft auftauchen lassen. Zum Beispiel angesichts der Einschränkung T : Ordering
, man braucht oft die Instanz von Ordering[T]
das erfüllt die Bedingung. Wie hier gezeigt , es ist möglich, auf den impliziten Wert mit der implicitly
-Methode oder einer etwas hilfreicheren context
-Methode 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) }