wake-up-neo.com

Was ist ein "Kontext gebunden" in Scala?

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.

106
Jesper

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
}
100
Robert Harvey

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.

138
Ben Lings

(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 
38
retronym

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) }
17
Aaron Novstrup