Scala hat keine typsicheren enums wie Java. Was wäre angesichts einer Reihe verwandter Konstanten die beste Möglichkeit in Scala, diese Konstanten darzustellen?
Warum nicht einfach Java Enum verwenden? Dies ist eines der wenigen Dinge, die ich immer noch lieber mit einfachem Java verwende.
Max
1
Ich habe einen kleinen Überblick über die Aufzählung und Alternativen von Scala geschrieben. Vielleicht finden Sie ihn nützlich: pedrorijo.com/blog/scala-enums/
Im Ernst, Anwendung sollte nicht verwendet werden. Es wurde NICHT behoben; Eine neue Klasse, App, wurde eingeführt, die nicht die von Schildmeijer erwähnten Probleme hat. Also "object foo erweitert App {...}" Und Sie haben über die Variable args sofortigen Zugriff auf Befehlszeilenargumente.
AmigoNico
scala.Enumeration (das ist, was Sie in Ihrem obigen Codebeispiel "object WeekDay" verwenden) bietet keinen erschöpfenden Mustervergleich. Ich habe alle verschiedenen Aufzählungsmuster untersucht, die derzeit in Scala verwendet werden, und in dieser StackOverflow-Antwort einen Überblick darüber gegeben (einschließlich eines neuen Musters, das das Beste aus beiden scala.Enumeration- und dem Muster "Sealed Trait + Case Object" bietet
chaotic3quilibrium
377
Ich muss sagen , dass das Beispiel der Scala Dokumentation herauskopiert durch skaffman oben in der Praxis ist von begrenztem Nutzen (Sie könnte genauso gut verwenden case objects).
Um etwas zu erhalten, das einem Java am ähnlichsten ist Enum(dh mit Sinn toStringund valueOfMethoden - vielleicht behalten Sie die Enum-Werte in einer Datenbank bei), müssen Sie es ein wenig ändern. Wenn Sie den Skaffman -Code verwendet haben:
@macias valueOf'Ersatz ist withName, der keine Option zurückgibt und einen NSE auslöst , wenn keine Übereinstimmung vorliegt . Was zum!
Bluu
6
@Bluu Sie können valueOf selbst hinzufügen: def valueOf (name: String) = WeekDay.values.find (_. ToString == name), um eine Option zu haben
zentriert am
@centr Wenn ich versuche, Map[Weekday.Weekday, Long]einen Wert zu erstellen und einen Wert hinzuzufügen Mon, gibt der Compiler einen ungültigen Typfehler aus . Erwarteter Wochentag. Wochentag gefunden Wert? Warum passiert das?
Sohaib
@ Sohaib Es sollte Map [Weekday.Value, Long] sein.
Mitte
99
Es gibt viele Möglichkeiten.
1) Verwenden Sie Symbole. Es gibt Ihnen jedoch keine Typensicherheit, abgesehen davon, dass Sie keine Nicht-Symbole akzeptieren, bei denen ein Symbol erwartet wird. Ich erwähne es hier nur der Vollständigkeit halber. Hier ist ein Anwendungsbeispiel:
def update(what:Symbol, where:Int, newValue:Array[Int]):MatrixInt=
what match{case'row=> replaceRow(where, newValue)case'col|'column=> replaceCol(where, newValue)case _ =>thrownewIllegalArgumentException}// At REPL:
scala>val a = unitMatrixInt(3)
a: teste7.MatrixInt=/100\|010|\001/
scala> a('row,1)= a.row(0)
res41: teste7.MatrixInt=/100\|100|\001/
scala> a('column,2)= a.row(0)
res42: teste7.MatrixInt=/101\|010|\000/
def update(what:Dimension, where:Int, newValue:Array[Int]):MatrixInt=
what match{caseRow=> replaceRow(where, newValue)caseColumn=> replaceCol(where, newValue)}// At REPL:
scala> a(Row,2)= a.row(1)<console>:13: error: not found: value Row
a(Row,2)= a.row(1)^
scala> a(Dimension.Row,2)= a.row(1)
res1: teste.MatrixInt=/100\|010|\010/
scala>importDimension._
importDimension._
scala> a(Row,2)= a.row(1)
res2: teste.MatrixInt=/100\|010|\010/
Leider wird nicht sichergestellt, dass alle Übereinstimmungen berücksichtigt werden. Wenn ich vergessen hätte, Zeile oder Spalte in das Match aufzunehmen, hätte mich der Scala-Compiler nicht gewarnt. Es gibt mir also eine gewisse Sicherheit, aber nicht so viel, wie man gewinnen kann.
Sie fragen sich vielleicht, warum Sie jemals eine Aufzählung anstelle von Fallobjekten verwenden sollten. In der Tat haben Fallobjekte viele Vorteile, wie hier. Die Enumeration-Klasse verfügt jedoch über viele Collection-Methoden, z. B. Elemente (Iterator in Scala 2.8), die einen Iterator, eine Map, eine FlatMap, einen Filter usw. zurückgeben.
Diese Antwort ist im Wesentlichen ein ausgewählter Teil dieses Artikels in meinem Blog.
"... Nicht-Symbole nicht akzeptieren, wenn ein Symbol erwartet wird"> Ich vermute, Sie meinen, dass SymbolInstanzen keine Leerzeichen oder Sonderzeichen haben dürfen. Die meisten Leute, die die SymbolKlasse zum ersten Mal treffen, denken das wahrscheinlich, sind aber tatsächlich falsch. Symbol("foo !% bar -* baz")kompiliert und läuft einwandfrei. Mit anderen Worten, Sie können perfekt SymbolInstanzen erstellen, die eine beliebige Zeichenfolge umschließen (Sie können dies einfach nicht mit dem syntaktischen Zucker "Einzelkoma" tun). Das einzige, was Symbolgarantiert, ist die Einzigartigkeit eines bestimmten Symbols, wodurch es geringfügig schneller verglichen und abgeglichen werden kann.
Régis Jean-Gilles
@ RégisJean-Gilles Nein, ich meine, Sie können beispielsweise kein a Stringals Argument an einen SymbolParameter übergeben.
Daniel C. Sobral
Ja, ich habe diesen Teil verstanden, aber es ist ein ziemlich strittiger Punkt, wenn Sie ihn durch eine Stringandere Klasse ersetzen , die im Grunde ein Wrapper um einen String ist und frei in beide Richtungen konvertiert werden kann (wie es der Fall ist Symbol). Ich denke, das haben Sie gemeint, als Sie sagten "Es gibt Ihnen keine Typensicherheit". Es war einfach nicht sehr klar, da OP ausdrücklich nach typsicheren Lösungen gefragt hat. Ich war mir nicht sicher, ob Sie zum Zeitpunkt des Schreibens wussten, dass es nicht nur nicht typsicher ist, da es sich überhaupt nicht um Aufzählungen handelt, sondern auchSymbol nicht einmal garantiert, dass das übergebene Argument keine Sonderzeichen enthält.
Régis Jean-Gilles
1
Wenn Sie sagen "Nicht-Symbole nicht akzeptieren, wenn ein Symbol erwartet wird", kann dies entweder als "Nicht akzeptieren von Werten, die keine Instanzen von Symbolen sind" (was offensichtlich wahr ist) oder "Nicht akzeptieren von Werten, die keine Symbole sind" gelesen werden einfache identifikatorähnliche Zeichenfolgen, auch bekannt als "Symbole" (was nicht wahr ist und ein Missverständnis ist, dass so ziemlich jeder das erste Mal auf Scala-Symbole stößt, da die erste Begegnung jedoch die spezielle 'fooNotation ist, die dies ausschließt Nicht-Bezeichner-Zeichenfolgen). Dies ist dieses Missverständnis, das ich für jeden zukünftigen Leser zerstreuen wollte.
Régis Jean-Gilles
@ RégisJean-Gilles Ich meinte das erstere, das offensichtlich wahr ist. Ich meine, es ist offensichtlich für jeden wahr, der an statisches Tippen gewöhnt ist. Damals gab es viele Diskussionen über die relativen Vorzüge der statischen und "dynamischen" Typisierung, und viele Leute, die sich für Scala interessierten, kamen aus einem dynamischen Typisierungshintergrund, daher dachte ich, dass dies nicht selbstverständlich war. Ich würde heutzutage nicht einmal daran denken, diese Bemerkung zu machen. Persönlich denke ich, dass Scalas Symbol hässlich und überflüssig ist und es niemals benutzt. Ich stimme Ihrem letzten Kommentar zu, da dies ein guter Punkt ist.
Daniel C. Sobral
52
Eine etwas weniger ausführliche Art, benannte Aufzählungen zu deklarieren:
Das Problem hierbei ist natürlich, dass Sie die Reihenfolge der Namen und Werte synchron halten müssen, was einfacher ist, wenn Name und Wert in derselben Zeile deklariert sind.
Dies sieht auf den ersten Blick sauberer aus, hat jedoch den Nachteil, dass der Betreuer die Reihenfolge beider Listen synchron halten muss. Für das Beispiel der Wochentage erscheint dies nicht wahrscheinlich. Im Allgemeinen kann jedoch ein neuer Wert eingefügt oder einer gelöscht werden, und die beiden Listen können nicht synchron sein. In diesem Fall können subtile Fehler auftreten.
Brent Faust
1
Laut vorherigem Kommentar besteht das Risiko, dass die beiden unterschiedlichen Listen stillschweigend nicht mehr synchron sind. Während es für Ihr aktuelles kleines Beispiel kein Problem ist, ist die Wahrscheinlichkeit, dass die beiden Listen stillschweigend nicht mehr synchron sind, wesentlich höher, wenn es viel mehr Mitglieder gibt (wie Dutzende bis Hunderte). Auch scala.Enumeration kann nicht von Scalas Kompilierungszeit profitieren. Ich habe eine StackOverflow-Antwort erstellt, die eine Lösung enthält, die eine Laufzeitprüfung durchführt, um sicherzustellen, dass die beiden Listen synchron bleiben: stackoverflow.com/a/25923651/501113
chaotic3quilibrium
17
Sie können anstelle der Aufzählung eine versiegelte abstrakte Klasse verwenden, zum Beispiel:
Eine versiegelte Eigenschaft mit Fallobjekten ist ebenfalls möglich.
Ashalynd
2
Das Muster "Versiegelte Merkmale + Fallobjekte" weist Probleme auf, die ich in einer StackOverflow-Antwort detailliert darstelle. Ich habe jedoch herausgefunden, wie alle Probleme im Zusammenhang mit diesem Muster behoben werden können
chaotic3quilibrium
7
habe gerade Enumeratum entdeckt . Es ist ziemlich erstaunlich und ebenso erstaunlich, dass es nicht bekannter ist!
Nachdem ich mich in Scala eingehend mit allen Optionen rund um "Aufzählungen" befasst hatte, veröffentlichte ich einen viel vollständigeren Überblick über diese Domain in einem anderen StackOverflow-Thread . Es enthält eine Lösung für das Muster "Sealed Trait + Case Object", bei dem ich das Problem mit der Reihenfolge der JVM-Klassen- / Objektinitialisierung gelöst habe.
Das Projekt ist wirklich gut mit Beispielen und Dokumentation
Nur dieses Beispiel aus ihren Dokumenten sollte Sie interessieren
import enumeratum._
sealedtraitGreetingextendsEnumEntryobjectGreetingextendsEnum[Greeting]{/*
`findValues` is a protected method that invokes a macro to find all `Greeting` object declarations inside an `Enum`
You use it to implement the `val values` member
*/val values = findValues
caseobjectHelloextendsGreetingcaseobjectGoodByeextendsGreetingcaseobjectHiextendsGreetingcaseobjectByeextendsGreeting}// Object Greeting has a `withName(name: String)` methodGreeting.withName("Hello")// => res0: Greeting = HelloGreeting.withName("Haro")// => java.lang.IllegalArgumentException: Haro is not a member of Enum (Hello, GoodBye, Hi, Bye)// A safer alternative would be to use `withNameOption(name: String)` method which returns an Option[Greeting]Greeting.withNameOption("Hello")// => res1: Option[Greeting] = Some(Hello)Greeting.withNameOption("Haro")// => res2: Option[Greeting] = None// It is also possible to use strings case insensitivelyGreeting.withNameInsensitive("HeLLo")// => res3: Greeting = HelloGreeting.withNameInsensitiveOption("HeLLo")// => res4: Option[Greeting] = Some(Hello)// Uppercase-only strings may also be usedGreeting.withNameUppercaseOnly("HELLO")// => res5: Greeting = HelloGreeting.withNameUppercaseOnlyOption("HeLLo")// => res6: Option[Greeting] = None// Similarly, lowercase-only strings may also be usedGreeting.withNameLowercaseOnly("hello")// => res7: Greeting = HelloGreeting.withNameLowercaseOnlyOption("hello")// => res8: Option[Greeting] = Some(Hello)
Antworten:
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
Beispiel Verwendung
quelle
Ich muss sagen , dass das Beispiel der Scala Dokumentation herauskopiert durch skaffman oben in der Praxis ist von begrenztem Nutzen (Sie könnte genauso gut verwenden
case object
s).Um etwas zu erhalten, das einem Java am ähnlichsten ist
Enum
(dh mit SinntoString
undvalueOf
Methoden - vielleicht behalten Sie die Enum-Werte in einer Datenbank bei), müssen Sie es ein wenig ändern. Wenn Sie den Skaffman -Code verwendet haben:Unter Verwendung der folgenden Erklärung:
Sie erhalten vernünftigere Ergebnisse:
quelle
valueOf
'Ersatz istwithName
, der keine Option zurückgibt und einen NSE auslöst , wenn keine Übereinstimmung vorliegt . Was zum!Map[Weekday.Weekday, Long]
einen Wert zu erstellen und einen Wert hinzuzufügenMon
, gibt der Compiler einen ungültigen Typfehler aus . Erwarteter Wochentag. Wochentag gefunden Wert? Warum passiert das?Es gibt viele Möglichkeiten.
1) Verwenden Sie Symbole. Es gibt Ihnen jedoch keine Typensicherheit, abgesehen davon, dass Sie keine Nicht-Symbole akzeptieren, bei denen ein Symbol erwartet wird. Ich erwähne es hier nur der Vollständigkeit halber. Hier ist ein Anwendungsbeispiel:
2) Klasse verwenden
Enumeration
:oder, wenn Sie es serialisieren oder anzeigen müssen:
Dies kann folgendermaßen verwendet werden:
Leider wird nicht sichergestellt, dass alle Übereinstimmungen berücksichtigt werden. Wenn ich vergessen hätte, Zeile oder Spalte in das Match aufzunehmen, hätte mich der Scala-Compiler nicht gewarnt. Es gibt mir also eine gewisse Sicherheit, aber nicht so viel, wie man gewinnen kann.
3) Fallobjekte:
Wenn ich jetzt einen Fall auf a
match
auslasse, warnt mich der Compiler:Es wird fast genauso verwendet und benötigt nicht einmal ein
import
:Sie fragen sich vielleicht, warum Sie jemals eine Aufzählung anstelle von Fallobjekten verwenden sollten. In der Tat haben Fallobjekte viele Vorteile, wie hier. Die Enumeration-Klasse verfügt jedoch über viele Collection-Methoden, z. B. Elemente (Iterator in Scala 2.8), die einen Iterator, eine Map, eine FlatMap, einen Filter usw. zurückgeben.
Diese Antwort ist im Wesentlichen ein ausgewählter Teil dieses Artikels in meinem Blog.
quelle
Symbol
Instanzen keine Leerzeichen oder Sonderzeichen haben dürfen. Die meisten Leute, die dieSymbol
Klasse zum ersten Mal treffen, denken das wahrscheinlich, sind aber tatsächlich falsch.Symbol("foo !% bar -* baz")
kompiliert und läuft einwandfrei. Mit anderen Worten, Sie können perfektSymbol
Instanzen erstellen, die eine beliebige Zeichenfolge umschließen (Sie können dies einfach nicht mit dem syntaktischen Zucker "Einzelkoma" tun). Das einzige, wasSymbol
garantiert, ist die Einzigartigkeit eines bestimmten Symbols, wodurch es geringfügig schneller verglichen und abgeglichen werden kann.String
als Argument an einenSymbol
Parameter übergeben.String
andere Klasse ersetzen , die im Grunde ein Wrapper um einen String ist und frei in beide Richtungen konvertiert werden kann (wie es der Fall istSymbol
). Ich denke, das haben Sie gemeint, als Sie sagten "Es gibt Ihnen keine Typensicherheit". Es war einfach nicht sehr klar, da OP ausdrücklich nach typsicheren Lösungen gefragt hat. Ich war mir nicht sicher, ob Sie zum Zeitpunkt des Schreibens wussten, dass es nicht nur nicht typsicher ist, da es sich überhaupt nicht um Aufzählungen handelt, sondern auchSymbol
nicht einmal garantiert, dass das übergebene Argument keine Sonderzeichen enthält.'foo
Notation ist, die dies ausschließt Nicht-Bezeichner-Zeichenfolgen). Dies ist dieses Missverständnis, das ich für jeden zukünftigen Leser zerstreuen wollte.Eine etwas weniger ausführliche Art, benannte Aufzählungen zu deklarieren:
Das Problem hierbei ist natürlich, dass Sie die Reihenfolge der Namen und Werte synchron halten müssen, was einfacher ist, wenn Name und Wert in derselben Zeile deklariert sind.
quelle
Sie können anstelle der Aufzählung eine versiegelte abstrakte Klasse verwenden, zum Beispiel:
quelle
habe gerade Enumeratum entdeckt . Es ist ziemlich erstaunlich und ebenso erstaunlich, dass es nicht bekannter ist!
quelle
Nachdem ich mich in Scala eingehend mit allen Optionen rund um "Aufzählungen" befasst hatte, veröffentlichte ich einen viel vollständigeren Überblick über diese Domain in einem anderen StackOverflow-Thread . Es enthält eine Lösung für das Muster "Sealed Trait + Case Object", bei dem ich das Problem mit der Reihenfolge der JVM-Klassen- / Objektinitialisierung gelöst habe.
quelle
Dotty (Scala 3) wird native Enums unterstützen. Überprüfen Sie hier und hier .
quelle
In Scala ist es sehr bequem mit https://github.com/lloydmeta/enumeratum
Das Projekt ist wirklich gut mit Beispielen und Dokumentation
Nur dieses Beispiel aus ihren Dokumenten sollte Sie interessieren
quelle