Scala ne possède pas enum
sécurisé par les caractères comme Java has. Étant donné un ensemble de constantes liées, quel serait le meilleur moyen dans Scala de représenter ces constantes?
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
Exemple d'utilisation
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
}
Je dois dire que l'exemple copié de la documentation Scala par skaffman ci-dessus est d'une utilité limitée en pratique (vous pouvez aussi bien utiliser case object
s).
Pour obtenir quelque chose qui ressemble le plus à un Java Enum
(c'est-à-dire avec les méthodes sensibles toString
et valueOf
, vous conservez peut-être les valeurs enum dans une base de données) vous devez le modifier un peu. Si vous aviez utilisé le code de skaffman:
WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString //returns Weekday(2)
Considérant qu’en utilisant la déclaration suivante:
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon = Value("Mon")
val Tue = Value("Tue")
... etc
}
Vous obtenez des résultats plus sensibles:
WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString //returns Tue
Il y a plusieurs façons de faire.
1) Utilisez des symboles. Cela ne vous apportera aucune sécurité, à part le fait de ne pas accepter les non-symboles lorsqu'un symbole est attendu. Je ne le mentionne ici que pour être complet. Voici un exemple d'utilisation:
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) Utilisation de la classe Enumeration
:
object Dimension extends Enumeration {
type Dimension = Value
val Row, Column = Value
}
ou, si vous avez besoin de le sérialiser ou de l'afficher:
object Dimension extends Enumeration("Row", "Column") {
type Dimension = Value
val Row, Column = Value
}
Cela peut être utilisé comme ceci:
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 /
Malheureusement, cela ne garantit pas que toutes les correspondances sont comptabilisées. Si j'avais oublié de mettre Row ou Column dans le match, le compilateur Scala ne m'aurait pas prévenu. Donc, cela me donne un peu de sécurité , mais pas autant qu'on peut en gagner.
3) objets de cas:
sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension
Maintenant, si je laisse un cas sur un match
, le compilateur m'avertira:
MatrixInt.scala:70: warning: match is not exhaustive!
missing combination Column
what match {
^
one warning found
Il est utilisé à peu près de la même manière, et n'a même pas besoin d'un 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 /
Vous pourriez alors vous demander pourquoi utiliser une énumération plutôt que des objets de casse. En fait, les objets de cas ont de nombreux avantages, comme ici. La classe Enumeration, cependant, comporte de nombreuses méthodes Collection, telles que des éléments (itérateur sur Scala 2.8), qui renvoie un itérateur, une carte, une flatmap, un filtre, etc.
Cette réponse est essentiellement une partie sélectionnée de cet article dans mon blog.
Une façon un peu moins verbeuse de déclarer des énumérations nommées:
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
Bien sûr, le problème ici est que vous devez garder l'ordre des noms et des valeurs synchronisé, ce qui est plus facile à faire si name et val sont déclarés sur la même ligne.
Vous pouvez utiliser une classe abstraite scellée au lieu de l'énumération, par exemple:
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))
}
}
je viens de découvrir enumeratum . c'est assez incroyable et tout aussi incroyable, ce n'est pas plus connu!
Après avoir effectué des recherches approfondies sur toutes les options concernant les "énumérations" dans Scala, j'ai posté un aperçu beaucoup plus complet de ce domaine sur un autre fil StackOverflow . Il inclut une solution au modèle "trait scellé + objet de cas" dans lequel j'ai résolu le problème d'ordre d'initialisation classe/objet de la machine virtuelle Java.
Dans Scala il est très à l'aise avec https://github.com/lloydmeta/enumeratum
Le projet est vraiment bon avec des exemples et de la documentation
Juste cet exemple de leurs documentations devrait vous intéresser
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)