Scala hat keine typsicheren enum
s wie Java. Was wäre angesichts einer Reihe verwandter Konstanten die beste Möglichkeit in Scala, diese Konstanten darzustellen?
Scala hat keine typsicheren enum
s wie Java. Was wäre angesichts einer Reihe verwandter Konstanten die beste Möglichkeit in Scala, diese Konstanten darzustellen?
Antworten:
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
Beispiel Verwendung
object Main extends App {
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}
import WeekDay._
def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)
WeekDay.values filter isWorkingDay foreach println
}
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 Sinn toString
und valueOf
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:
WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString //returns Weekday(2)
Unter Verwendung der folgenden Erklärung:
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon = Value("Mon")
val Tue = Value("Tue")
... etc
}
Sie erhalten vernünftigere Ergebnisse:
WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString //returns Tue
valueOf
'Ersatz ist withName
, 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ügen Mon
, 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:
def update(what: Symbol, where: Int, newValue: Array[Int]): MatrixInt =
what match {
case 'row => replaceRow(where, newValue)
case 'col | 'column => replaceCol(where, newValue)
case _ => throw new IllegalArgumentException
}
// At REPL:
scala> val a = unitMatrixInt(3)
a: teste7.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /
scala> a('row, 1) = a.row(0)
res41: teste7.MatrixInt =
/ 1 0 0 \
| 1 0 0 |
\ 0 0 1 /
scala> a('column, 2) = a.row(0)
res42: teste7.MatrixInt =
/ 1 0 1 \
| 0 1 0 |
\ 0 0 0 /
2) Klasse verwenden Enumeration
:
object Dimension extends Enumeration {
type Dimension = Value
val Row, Column = Value
}
oder, wenn Sie es serialisieren oder anzeigen müssen:
object Dimension extends Enumeration("Row", "Column") {
type Dimension = Value
val Row, Column = Value
}
Dies kann folgendermaßen verwendet werden:
def update(what: Dimension, where: Int, newValue: Array[Int]): MatrixInt =
what match {
case Row => replaceRow(where, newValue)
case Column => 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 =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /
scala> import Dimension._
import Dimension._
scala> a(Row, 2) = a.row(1)
res2: teste.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /
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:
sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension
Wenn ich jetzt einen Fall auf a match
auslasse, warnt mich der Compiler:
MatrixInt.scala:70: warning: match is not exhaustive!
missing combination Column
what match {
^
one warning found
Es wird fast genauso verwendet und benötigt nicht einmal ein import
:
scala> val a = unitMatrixInt(3)
a: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /
scala> a(Row,2) = a.row(0)
res15: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 1 0 0 /
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.
Symbol
Instanzen keine Leerzeichen oder Sonderzeichen haben dürfen. Die meisten Leute, die die Symbol
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 perfekt Symbol
Instanzen erstellen, die eine beliebige Zeichenfolge umschließen (Sie können dies einfach nicht mit dem syntaktischen Zucker "Einzelkoma" tun). Das einzige, was Symbol
garantiert, ist die Einzigartigkeit eines bestimmten Symbols, wodurch es geringfügig schneller verglichen und abgeglichen werden kann.
String
als Argument an einen Symbol
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 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 auch Symbol
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:
object WeekDay extends Enumeration("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") {
type WeekDay = Value
val Sun, Mon, Tue, Wed, Thu, Fri, Sat = Value
}
WeekDay.valueOf("Wed") // returns Some(Wed)
WeekDay.Fri.toString // returns Fri
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.
Sie können anstelle der Aufzählung eine versiegelte abstrakte Klasse verwenden, zum Beispiel:
sealed abstract class Constraint(val name: String, val verifier: Int => Boolean)
case object NotTooBig extends Constraint("NotTooBig", (_ < 1000))
case object NonZero extends Constraint("NonZero", (_ != 0))
case class NotEquals(x: Int) extends Constraint("NotEquals " + x, (_ != x))
object Main {
def eval(ctrs: Seq[Constraint])(x: Int): Boolean =
(true /: ctrs){ case (accum, ctr) => accum && ctr.verifier(x) }
def main(args: Array[String]) {
val ctrs = NotTooBig :: NotEquals(5) :: Nil
val evaluate = eval(ctrs) _
println(evaluate(3000))
println(evaluate(3))
println(evaluate(5))
}
}
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.
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
import enumeratum._
sealed trait Greeting extends EnumEntry
object Greeting extends Enum[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
case object Hello extends Greeting
case object GoodBye extends Greeting
case object Hi extends Greeting
case object Bye extends Greeting
}
// Object Greeting has a `withName(name: String)` method
Greeting.withName("Hello")
// => res0: Greeting = Hello
Greeting.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 insensitively
Greeting.withNameInsensitive("HeLLo")
// => res3: Greeting = Hello
Greeting.withNameInsensitiveOption("HeLLo")
// => res4: Option[Greeting] = Some(Hello)
// Uppercase-only strings may also be used
Greeting.withNameUppercaseOnly("HELLO")
// => res5: Greeting = Hello
Greeting.withNameUppercaseOnlyOption("HeLLo")
// => res6: Option[Greeting] = None
// Similarly, lowercase-only strings may also be used
Greeting.withNameLowercaseOnly("hello")
// => res7: Greeting = Hello
Greeting.withNameLowercaseOnlyOption("hello")
// => res8: Option[Greeting] = Some(Hello)