In Scala können wir mindestens zwei Methoden verwenden, um vorhandene oder neue Typen nachzurüsten. Angenommen, wir möchten ausdrücken, dass etwas mit einem quantifiziert werden kann Int
. Wir können das folgende Merkmal definieren.
Implizite Konvertierung
trait Quantifiable{ def quantify: Int }
Und dann können wir implizite Konvertierungen verwenden, um z. B. Zeichenfolgen und Listen zu quantifizieren.
implicit def string2quant(s: String) = new Quantifiable{
def quantify = s.size
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{
val quantify = l.size
}
Nachdem wir diese importiert haben, können wir die Methode quantify
für Zeichenfolgen und Listen aufrufen . Beachten Sie, dass die quantifizierbare Liste ihre Länge speichert, sodass das teure Durchlaufen der Liste bei nachfolgenden Aufrufen von vermieden wird quantify
.
Typklassen
Eine Alternative besteht darin, einen "Zeugen" zu definieren Quantified[A]
, der besagt, dass ein Typ A
quantifiziert werden kann.
trait Quantified[A] { def quantify(a: A): Int }
Wir stellen dann Instanzen dieser Typklasse für String
und List
irgendwo zur Verfügung.
implicit val stringQuantifiable = new Quantified[String] {
def quantify(s: String) = s.size
}
Und wenn wir dann eine Methode schreiben, die ihre Argumente quantifizieren muss, schreiben wir:
def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) =
as.map(ev.quantify).sum
Oder verwenden Sie die kontextgebundene Syntax:
def sumQuantities[A: Quantified](as: List[A]) =
as.map(implicitly[Quantified[A]].quantify).sum
Aber wann welche Methode anwenden?
Nun kommt die Frage. Wie kann ich mich zwischen diesen beiden Konzepten entscheiden?
Was mir bisher aufgefallen ist.
Typklassen
- Typklassen ermöglichen die nette kontextgebundene Syntax
- Mit Typklassen erstelle ich nicht bei jeder Verwendung ein neues Wrapper-Objekt
- Die kontextgebundene Syntax funktioniert nicht mehr, wenn die Typklasse mehrere Typparameter hat. Stellen Sie sich vor, ich möchte Dinge nicht nur mit ganzen Zahlen, sondern auch mit Werten eines allgemeinen Typs quantifizieren
T
. Ich möchte eine Typklasse erstellenQuantified[A,T]
implizite Konvertierung
- Da ich ein neues Objekt erstelle, kann ich dort Werte zwischenspeichern oder eine bessere Darstellung berechnen. Aber sollte ich das vermeiden, da es mehrmals vorkommen kann und eine explizite Konvertierung wahrscheinlich nur einmal aufgerufen wird?
Was ich von einer Antwort erwarte
Stellen Sie einen (oder mehrere) Anwendungsfälle vor, bei denen der Unterschied zwischen beiden Konzepten von Bedeutung ist, und erklären Sie, warum ich einen dem anderen vorziehen würde. Es wäre auch ohne Beispiel schön, die Essenz der beiden Konzepte und ihre Beziehung zueinander zu erklären.
quelle
size
einer Liste in einem Wert und sagen, dass es das teure Durchlaufen der Liste bei nachfolgenden zu quantifizierenden Aufrufen vermeidet, aber bei jedem Aufruf anquantify
dielist2quantifiable
wird ausgelöst alles noch einmal, wodurchQuantifiable
dasquantify
Eigentum wieder hergestellt und neu berechnet wird . Ich sage, dass es tatsächlich keine Möglichkeit gibt, die Ergebnisse mit impliziten Konvertierungen zwischenzuspeichern.Antworten:
Ich möchte mein Material nicht von Scala In Depth duplizieren , aber ich denke, es ist erwähnenswert, dass Typklassen / Typmerkmale unendlich flexibler sind.
hat die Möglichkeit, seine lokale Umgebung nach einer Standardtypklasse zu durchsuchen. Ich kann das Standardverhalten jedoch jederzeit auf zwei Arten überschreiben:
Hier ist ein Beispiel:
Dies macht Typklassen unendlich flexibler. Eine andere Sache ist, dass Typklassen / Merkmale die implizite Suche besser unterstützen.
Wenn Sie in Ihrem ersten Beispiel eine implizite Ansicht verwenden, führt der Compiler eine implizite Suche durch nach:
Welches wird auf
Function1
das Begleitobjekt und dasInt
Begleitobjekt schauen .Beachten Sie, dass
Quantifiable
sich die implizite Suche nirgends befindet . Dies bedeutet, dass Sie die implizite Ansicht in ein Paketobjekt einfügen oder in den Bereich importieren müssen. Es ist mehr Arbeit, sich daran zu erinnern, was los ist.Andererseits ist eine Typklasse explizit . Sie sehen, wonach es sucht, in der Methodensignatur. Sie haben auch eine implizite Suche nach
welches in
Quantifiable
das Begleitobjekt undInt
das Begleitobjekt schauen wird . Dies bedeutet, dass Sie Standardeinstellungen angeben können und neue Typen (wie eineMyString
Klasse) einen Standard in ihrem Begleitobjekt angeben können, der implizit durchsucht wird.Im Allgemeinen verwende ich Typklassen. Sie sind für das erste Beispiel unendlich flexibler. Der einzige Ort, an dem ich implizite Konvertierungen verwende, ist die Verwendung einer API-Ebene zwischen einem Scala-Wrapper und einer Java-Bibliothek. Selbst dies kann "gefährlich" sein, wenn Sie nicht vorsichtig sind.
quelle
Ein Kriterium, das ins Spiel kommen kann, ist, wie sich die neue Funktion "anfühlen" soll. Mit impliziten Konvertierungen können Sie es so aussehen lassen, als wäre es nur eine andere Methode:
... bei der Verwendung von Typklassen sieht es immer so aus, als würden Sie eine externe Funktion aufrufen:
Eine Sache, die Sie mit Typklassen und nicht mit impliziten Konvertierungen erreichen können, ist das Hinzufügen von Eigenschaften zu einem Typ und nicht zu einer Instanz eines Typs. Sie können dann auf diese Eigenschaften zugreifen, auch wenn keine Instanz des Typs verfügbar ist. Ein kanonisches Beispiel wäre:
Dieses Beispiel zeigt auch, wie eng die Konzepte miteinander verbunden sind: Typklassen wären bei weitem nicht so nützlich, wenn es keinen Mechanismus gäbe, um unendlich viele ihrer Instanzen zu erzeugen. Ohne die
implicit
Methode (zugegebenermaßen keine Konvertierung) hätte ich nur endlich viele Typen dieDefault
Eigenschaft haben können.quelle
default
für zukünftige Leser hinzugefügt .Sie können sich den Unterschied zwischen den beiden Techniken analog zur Funktionsanwendung vorstellen, nur mit einem benannten Wrapper. Beispielsweise:
Eine Instanz der ersteren kapselt eine Funktion vom Typ
A => Int
, während eine Instanz der letzteren bereits auf eine angewendet wurdeA
. Sie könnten das Muster fortsetzen ...man könnte sich also so
Foo1[B]
etwas wie die teilweise AnwendungFoo2[A, B]
auf eineA
Instanz vorstellen. Ein gutes Beispiel dafür wurde von Miles Sabin als "Functional Dependencies in Scala" geschrieben .Mein Punkt ist also wirklich, dass im Prinzip:
quelle