Gibt es Best-Practice-Richtlinien für die Verwendung von Fallklassen (oder Fallobjekten) im Vergleich zur Erweiterung der Aufzählung in Scala?
Sie scheinen einige der gleichen Vorteile zu bieten.
scala
enumeration
case-class
Alex Miller
quelle
quelle
enum
(für Mitte 2020).Antworten:
Ein großer Unterschied besteht darin, dass
Enumeration
die Instanziierung von einemname
String unterstützt wird. Beispielsweise:Dann können Sie tun:
Dies ist nützlich, wenn Sie Aufzählungen (z. B. in einer Datenbank) beibehalten oder aus Daten erstellen möchten, die sich in Dateien befinden. Im Allgemeinen finde ich jedoch, dass Aufzählungen in Scala etwas ungeschickt sind und das Gefühl eines umständlichen Add-Ons haben, weshalb ich jetzt eher
case object
s verwende. Acase object
ist flexibler als eine Aufzählung:Jetzt habe ich den Vorteil ...
Wie @ chaotic3quilibrium hervorhob (mit einigen Korrekturen, um das Lesen zu erleichtern):
Um die anderen Antworten hier weiterzuverfolgen, sind die Hauptnachteile von
case object
s überEnumeration
s:Kann nicht über alle Instanzen der "Aufzählung" iterieren . Dies ist sicherlich der Fall, aber ich habe es in der Praxis als äußerst selten empfunden, dass dies erforderlich ist.
Kann nicht einfach aus dem dauerhaften Wert instanziieren . Dies gilt auch, aber außer bei großen Aufzählungen (z. B. allen Währungen) bedeutet dies keinen großen Aufwand.
quelle
trade.ccy
im Beispiel des versiegelten Merkmals übereinstimmt, nicht .case
object
erzeugen Sie keinen größeren (~ 4x) Code-Footprint alsEnumeration
? Nützliche Unterscheidung insbesondere fürscala.js
Projekte mit geringem Platzbedarf.UPDATE: Es wurde eine neue makrobasierte Lösung erstellt, die der unten beschriebenen Lösung weit überlegen ist. Ich empfehle dringend, diese neue makrobasierte Lösung zu verwenden . Und es scheint, dass Pläne für Dotty diesen Stil der Enum-Lösung zu einem Teil der Sprache machen werden. Whoohoo!
Zusammenfassung:
Es gibt drei grundlegende Muster für den Versuch, Java
Enum
in einem Scala-Projekt zu reproduzieren . Zwei der drei Muster; direkt mit JavaEnum
undscala.Enumeration
können den umfassenden Mustervergleich von Scala nicht aktivieren. Und der dritte; "Sealed Trait + Case Object", tut dies ... hat jedoch Komplikationen bei der Initialisierung von JVM-Klassen / Objekten, die zu einer inkonsistenten Generierung des Ordnungsindex führen.Ich habe eine Lösung mit zwei Klassen erstellt. Enumeration und EnumerationDecorated befinden sich in dieser Übersicht . Ich habe den Code nicht in diesen Thread gepostet, da die Datei für die Aufzählung ziemlich groß war (+400 Zeilen - enthält viele Kommentare, die den Implementierungskontext erklären).
Details:
Die Frage, die Sie stellen, ist ziemlich allgemein; "... wann man
case
Klassen benutzt oderobjects
erweitert[scala.]Enumeration
". Und es stellt sich heraus, dass es VIELE mögliche Antworten gibt, wobei jede Antwort von den Feinheiten der spezifischen Projektanforderungen abhängt, die Sie haben. Die Antwort kann auf drei Grundmuster reduziert werden.Stellen wir zunächst sicher, dass wir von der gleichen Grundidee einer Aufzählung ausgehen. Definieren wir eine Aufzählung hauptsächlich anhand der
Enum
ab Java 5 (1.5) bereitgestellten :Enum
, wäre es schön, Scalas Musterübereinstimmungsprüfung für eine Aufzählung explizit nutzen zu könnenSchauen wir uns als nächstes die zusammengefassten Versionen der drei am häufigsten veröffentlichten Lösungsmuster an:
A) Tatsächlich direkt mit Java-
Enum
Muster (in einem gemischten Scala / Java-Projekt):Die folgenden Elemente aus der Aufzählungsdefinition sind nicht verfügbar:
Bei meinen aktuellen Projekten habe ich nicht den Vorteil, die Risiken rund um den gemischten Scala / Java-Projektpfad einzugehen. Und selbst wenn ich mich für ein gemischtes Projekt entscheiden könnte, ist Punkt 7 entscheidend, damit ich Probleme mit der Kompilierungszeit erkennen kann, wenn ich Aufzählungselemente hinzufüge / entferne oder neuen Code schreibe, um mit vorhandenen Aufzählungsmitgliedern umzugehen.
B) Verwenden des "
sealed trait
+case objects
" - Musters:Die folgenden Elemente aus der Aufzählungsdefinition sind nicht verfügbar:
Es ist fraglich, ob es die Aufzählungsdefinitionspunkte 5 und 6 wirklich erfüllt. Für 5 ist es eine Strecke zu behaupten, dass es effizient ist. Für 6 ist es nicht einfach, zusätzliche zugehörige Singleton-Daten zu speichern.
C) Verwenden des
scala.Enumeration
Musters (inspiriert von dieser StackOverflow-Antwort ):Die folgenden Elemente aus der Aufzählungsdefinition sind nicht verfügbar (identisch mit der Liste für die direkte Verwendung der Java-Enumeration):
Auch für meine aktuellen Projekte ist Punkt 7 von entscheidender Bedeutung, damit ich Probleme mit der Kompilierungszeit erkennen kann, wenn ich Aufzählungselemente hinzufüge / entferne oder neuen Code für vorhandene Aufzählungsmitglieder schreibe.
Angesichts der obigen Definition einer Aufzählung funktioniert keine der drei oben genannten Lösungen, da sie nicht alles bieten, was in der obigen Aufzählungsdefinition beschrieben ist:
Jede dieser Lösungen kann eventuell überarbeitet / erweitert / überarbeitet werden, um zu versuchen, einige der fehlenden Anforderungen jedes einzelnen zu decken. Weder Java
Enum
noch diescala.Enumeration
Lösungen können jedoch ausreichend erweitert werden, um Punkt 7 bereitzustellen. Für meine eigenen Projekte ist dies einer der überzeugendsten Werte bei der Verwendung eines geschlossenen Typs in Scala. Ich bevorzuge Warnungen / Fehler zur Kompilierungszeit, um anzuzeigen, dass ich eine Lücke / ein Problem in meinem Code habe, anstatt es aus einer Ausnahme / einem Fehler zur Produktionslaufzeit herauslesen zu müssen.In diesem Zusammenhang habe ich mich daran gemacht, mit dem
case object
Pfad zu arbeiten , um zu sehen, ob ich eine Lösung finden kann, die alle oben genannten Aufzählungsdefinitionen abdeckt. Die erste Herausforderung bestand darin, den Kern des Problems der JVM-Klassen- / Objektinitialisierung durchzuarbeiten (das in diesem StackOverflow-Beitrag ausführlich behandelt wird ). Und ich konnte endlich eine Lösung finden.Als meine Lösung sind zwei Eigenschaften; Enumeration und EnumerationDecorated , und da das
Enumeration
Merkmal mehr als 400 Zeilen lang ist (viele Kommentare, die den Kontext erklären), verzichte ich darauf, es in diesen Thread einzufügen (was dazu führen würde, dass es die Seite erheblich ausdehnt). Für Details springen Sie bitte direkt zum Kern .So sieht die Lösung aus, wenn dieselbe Datenidee wie oben (vollständig kommentierte Version hier verfügbar ) verwendet und in implementiert wird
EnumerationDecorated
.Dies ist ein Beispiel für die Verwendung eines neuen Paares von Aufzählungsmerkmalen, die ich erstellt habe (in dieser Übersicht ), um alle in der Aufzählungsdefinition gewünschten und beschriebenen Funktionen zu implementieren.
Ein Anliegen ist, dass die Namen der Aufzählungsmitglieder wiederholt werden müssen (
decorationOrderedSet
im obigen Beispiel). Obwohl ich es auf eine einzige Wiederholung reduziert habe, konnte ich aufgrund zweier Probleme nicht sehen, wie ich es noch weniger machen kann:getClass.getDeclaredClasses
hat eine undefinierte Reihenfolge (und es ist ziemlich unwahrscheinlich, dass er in derselben Reihenfolge wie diecase object
Deklarationen im Quellcode vorliegt).Angesichts dieser beiden Probleme musste ich den Versuch aufgeben, eine implizite Bestellung zu generieren, und ich musste explizit verlangen, dass der Kunde sie mit einer Art geordnetem Satz definiert und deklariert. Da die Scala-Sammlungen keine Implementierung für geordnete Sätze haben, konnte ich am besten eine
List
Laufzeitprüfung verwenden und dann überprüfen, ob es sich wirklich um einen Satz handelt. Es ist nicht so, wie ich es vorgezogen hätte, dies erreicht zu haben.Und angesichts der Konstruktion dieser zweiten Liste / set Bestellung erforderlich
val
, angesichts derChessPiecesEnhancedDecorated
obigen Beispiel war es möglich , hinzufügencase object PAWN2 extends Member
und dann vergessen , hinzuzufügen ,Decoration(PAWN2,'P2', 2)
zudecorationOrderedSet
. Daher wird zur Laufzeit überprüft, ob die Liste nicht nur eine Menge ist, sondern ALLE Fallobjekte enthält, die die Liste erweiternsealed trait Member
. Das war eine besondere Form der Reflexion / Makro-Hölle.Bitte hinterlassen Sie Kommentare und / oder Feedback zum Kern .
quelle
org.scalaolio.util.Enumeration
undorg.scalaolio.util.EnumerationDecorated
: scalaolio.orgFallobjekte geben bereits ihren Namen für ihre toString-Methoden zurück, sodass eine separate Übergabe nicht erforderlich ist. Hier ist eine Version ähnlich der von jho (der Kürze halber wurden die Convenience-Methoden weggelassen):
Objekte sind faul; Wenn wir stattdessen vals verwenden, können wir die Liste löschen, müssen aber den Namen wiederholen:
Wenn Ihnen Betrug nichts ausmacht, können Sie Ihre Aufzählungswerte mithilfe der Reflection-API oder ähnlichem wie Google Reflections vorladen. Nicht faule Fallobjekte bieten Ihnen die sauberste Syntax:
Schön und sauber, mit allen Vorteilen von Fallklassen und Java-Aufzählungen. Persönlich definiere ich die Aufzählungswerte außerhalb des Objekts, um besser mit dem idiomatischen Scala-Code übereinzustimmen:
quelle
Currency.values
, erhalte ich nur Werte zurück, auf die ich zuvor zugegriffen habe. Gibt es einen Weg daran vorbei?Die Verwendung von Fallklassen gegenüber Aufzählungen bietet folgende Vorteile:
Die Verwendung von Aufzählungen anstelle von Fallklassen bietet folgende Vorteile:
Wenn Sie also nur eine Liste einfacher Konstanten nach Namen benötigen, verwenden Sie im Allgemeinen Aufzählungen. Wenn Sie andernfalls etwas Komplexeres benötigen oder die zusätzliche Sicherheit des Compilers Ihnen mitteilen möchte, ob alle Übereinstimmungen angegeben sind, verwenden Sie Fallklassen.
quelle
UPDATE: Der folgende Code hat einen Fehler, der hier beschrieben wird . Das folgende Testprogramm funktioniert, aber wenn Sie DayOfWeek.Mon (zum Beispiel) vor DayOfWeek selbst verwenden, schlägt dies fehl, da DayOfWeek nicht initialisiert wurde (die Verwendung eines inneren Objekts bewirkt nicht, dass ein äußeres Objekt initialisiert wird). Sie können diesen Code weiterhin verwenden, wenn Sie etwas wie
val enums = Seq( DayOfWeek )
in Ihrer Hauptklasse tun und die Initialisierung Ihrer Aufzählungen erzwingen, oder Sie können die Modifikationen von chaotic3quilibrium verwenden. Ich freue mich auf eine makrobasierte Aufzählung!Falls Sie es wollen
dann kann das Folgende von Interesse sein. Feedback willkommen.
In dieser Implementierung gibt es abstrakte Enum- und EnumVal-Basisklassen, die Sie erweitern. Wir werden diese Klassen in einer Minute sehen, aber zuerst würden Sie eine Aufzählung folgendermaßen definieren:
Beachten Sie, dass Sie jeden Enum-Wert verwenden müssen (rufen Sie die Apply-Methode auf), um ihn zum Leben zu erwecken. [Ich wünschte, innere Objekte wären nicht faul, es sei denn, ich fordere sie ausdrücklich dazu auf. Meiner Ansicht nach.]
Wir könnten natürlich Methoden / Daten zu DayOfWeek, Val oder den einzelnen Fallobjekten hinzufügen, wenn wir dies wünschen.
Und so würden Sie eine solche Aufzählung verwenden:
Folgendes erhalten Sie beim Kompilieren:
Sie können "Tagesübereinstimmung" durch "(Tag: @unchecked) Übereinstimmung" ersetzen, wenn Sie solche Warnungen nicht möchten, oder am Ende einfach einen Sammelfall einfügen.
Wenn Sie das obige Programm ausführen, erhalten Sie folgende Ausgabe:
Da die Liste und die Karten unveränderlich sind, können Sie Elemente leicht entfernen, um Teilmengen zu erstellen, ohne die Aufzählung selbst zu beschädigen.
Hier ist die Enum-Klasse selbst (und EnumVal darin):
Und hier ist eine fortgeschrittenere Verwendung, die die IDs steuert und Daten / Methoden zur Val-Abstraktion und zur Aufzählung selbst hinzufügt:
quelle
var
] ist eine grenzwertige Todsünde in der FP-Welt" - Ich glaube nicht, dass die Meinung allgemein akzeptiert wird.Ich habe hier eine schöne einfache Bibliothek, mit der Sie versiegelte Merkmale / Klassen als Enum-Werte verwenden können, ohne Ihre eigene Werteliste führen zu müssen. Es basiert auf einem einfachen Makro, das nicht vom Buggy abhängig ist
knownDirectSubclasses
.https://github.com/lloydmeta/enumeratum
quelle
Update März 2017: Wie von Anthony Accioly kommentiert , wurde die
scala.Enumeration/enum
PR geschlossen.Dotty (Compiler der nächsten Generation für Scala) wird die Führung übernehmen, obwohl Dotty 1970 und Martin Oderskys PR 1958 .
Hinweis: Es gibt jetzt (August 2016, 6+ Jahre später) einen Vorschlag zum Entfernen
scala.Enumeration
: PR 5352quelle
Ein weiterer Nachteil von Fallklassen gegenüber Aufzählungen, wenn Sie über alle Instanzen hinweg iterieren oder filtern müssen. Dies ist eine integrierte Funktion der Aufzählung (und auch der Java-Aufzählungen), während Fallklassen diese Funktion nicht automatisch unterstützen.
Mit anderen Worten: "Es gibt keine einfache Möglichkeit, eine Liste der gesamten Aufzählungswerte mit Fallklassen abzurufen."
quelle
Wenn Sie es ernst meinen, die Interoperabilität mit anderen JVM-Sprachen (z. B. Java) aufrechtzuerhalten, ist es am besten, Java-Enums zu schreiben. Diese arbeiten transparent sowohl mit Scala- als auch mit Java-Code, was mehr ist, als für
scala.Enumeration
oder Fallobjekte gesagt werden kann. Lassen Sie uns nicht für jedes neue Hobbyprojekt auf GitHub eine neue Aufzählungsbibliothek haben, wenn dies vermieden werden kann!quelle
Ich habe verschiedene Versionen gesehen, in denen eine Fallklasse eine Aufzählung nachahmt. Hier ist meine Version:
Auf diese Weise können Sie Fallklassen erstellen, die wie folgt aussehen:
Vielleicht könnte sich jemand einen besseren Trick einfallen lassen, als einfach eine Fallklasse wie ich zur Liste hinzuzufügen. Das war alles, was ich mir damals einfallen lassen konnte.
quelle
Ich habe diese beiden Optionen die letzten Male hin und her gegangen, als ich sie gebraucht habe. Bis vor kurzem habe ich die Option für versiegelte Merkmale / Fallobjekte bevorzugt.
1) Erklärung zur Scala-Aufzählung
2) Versiegelte Eigenschaften + Fallobjekte
Während keines von beiden wirklich alle Anforderungen einer Java-Aufzählung erfüllt, sind im Folgenden die Vor- und Nachteile aufgeführt:
Scala-Aufzählung
Vorteile: -Funktionen zum Instanziieren mit Option oder zur direkten Annahme einer genauen (einfacher beim Laden aus einem persistenten Speicher) -Iteration über alle möglichen Werte wird unterstützt
Nachteile: -Kompilierungswarnung für nicht erschöpfende Suche wird nicht unterstützt (macht Mustervergleich weniger ideal)
Fallobjekte / Versiegelte Merkmale
Vorteile: - Mit versiegelten Merkmalen können wir einige Werte vorab instanziieren, während andere zum Zeitpunkt der Erstellung injiziert werden können. - Volle Unterstützung für den Mustervergleich (definierte Methoden anwenden / nicht anwenden).
Nachteile: - Ausgehend von einem persistenten Speicher - Sie müssen hier häufig den Mustervergleich verwenden oder eine eigene Liste aller möglichen 'Aufzählungswerte' definieren.
Was mich letztendlich dazu brachte, meine Meinung zu ändern, war so etwas wie der folgende Ausschnitt:
Die
.get
Aufrufe waren abscheulich - stattdessen kann ich mit der Aufzählung einfach die withName-Methode für die Aufzählung wie folgt aufrufen:Daher denke ich, dass ich es in Zukunft vorziehen werde, Aufzählungen zu verwenden, wenn auf die Werte aus einem Repository zugegriffen werden soll, und ansonsten Fallobjekte / versiegelte Merkmale.
quelle
Ich bevorzuge
case objects
(es ist eine Frage der persönlichen Präferenz). Um die mit diesem Ansatz verbundenen Probleme zu bewältigen (Zeichenfolge analysieren und über alle Elemente iterieren), habe ich einige Zeilen hinzugefügt, die nicht perfekt, aber effektiv sind.Ich füge Ihnen den Code hier ein und erwarte, dass er nützlich sein könnte und dass andere ihn verbessern könnten.
quelle
Für diejenigen, die noch suchen, wie GatesDas Antwort funktioniert : Sie können einfach auf das case-Objekt verweisen, nachdem Sie es deklariert haben, um es zu instanziieren:
quelle
Ich denke, der größte Vorteil von
case classes
Overenumerations
ist, dass Sie Typklassenmuster, auch Ad-hoc-Polymorphysmus genannt, verwenden können . Sie müssen keine Aufzählungen wie:Stattdessen haben Sie so etwas wie:
quelle