Scala n'a pas de type sûr enum
comme Java. Étant donné un ensemble de constantes liées, quelle serait la meilleure façon dans Scala de représenter ces constantes?
Scala n'a pas de type sûr enum
comme Java. Étant donné un ensemble de constantes liées, quelle serait la meilleure façon dans Scala de représenter ces constantes?
Réponses:
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 dans la pratique (vous pourriez aussi bien utiliser case object
s).
Afin d'obtenir quelque chose qui ressemble le plus à un Java Enum
(c'est-à-dire avec des méthodes sensées toString
et valueOf
- peut-être que vous persistez les valeurs d'énumération 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)
Attendu 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
valueOf
Le remplacement de @macias est withName
, qui ne retourne pas d'option, et lance un NSE s'il n'y a pas de correspondance. Qu'est-ce que!
Map[Weekday.Weekday, Long]
et d'ajouter une valeur, Mon
le compilateur génère une erreur de type non valide. Jour de semaine prévu. Pourquoi cela arrive-t-il?
Il y a plusieurs façons de faire.
1) Utilisez des symboles. Cependant, cela ne vous donnera aucune sécurité de type, à part qu'il n'accepte pas les non-symboles où 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 devez le sérialiser ou 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 tous les matchs sont pris en compte. Si j'avais oublié de mettre Row ou Column dans le match, le compilateur Scala ne m'aurait pas prévenu. Cela me donne donc une certaine sécurité de type, mais pas autant que ce qui peut être gagné.
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 a 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 vous demandez peut-être alors pourquoi utiliser une énumération plutôt que des objets de cas. En fait, les objets de cas présentent de nombreux avantages, comme ici. La classe Enumeration, cependant, possède de nombreuses méthodes Collection, telles que des éléments (itérateur sur Scala 2.8), qui renvoie un Iterator, une carte, un flatMap, un filtre, etc.
Cette réponse est essentiellement une partie sélectionnée de cet article dans mon blog.
Symbol
instances ne peuvent pas avoir d'espaces ou de caractères spéciaux. La plupart des gens lors de leur première rencontre avec la Symbol
classe le pensent probablement, mais c'est en fait incorrect. Symbol("foo !% bar -* baz")
compile et fonctionne parfaitement bien. En d'autres termes, vous pouvez parfaitement créer des Symbol
instances enveloppant n'importe quelle chaîne (vous ne pouvez tout simplement pas le faire avec le sucre syntaxique "coma unique"). La seule chose qui Symbol
garantit est l'unicité d'un symbole donné, ce qui le rend légèrement plus rapide à comparer et à faire correspondre.
String
, par exemple, comme argument à un Symbol
paramètre.
String
par une autre classe qui est essentiellement un wrapper autour d'une chaîne et peut être librement convertie dans les deux sens (comme c'est le cas pour Symbol
). Je suppose que c'est ce que vous vouliez dire en disant "Cela ne vous donnera aucune sécurité de type", ce n'était tout simplement pas très clair étant donné que OP a explicitement demandé des solutions sûres de type. Je ne savais pas si au moment de la rédaction de cet article, vous saviez que non seulement ce type n'est pas sûr car ce ne sont pas du tout des énumérations, mais aussi Symbol
qu'il ne garantit même pas que l'argument passé n'aura pas de caractères spéciaux.
'foo
notation qui ne se opposent chaînes non identifiantes). C'est cette idée fausse que je voulais dissiper pour tout futur lecteur.
Une façon un peu moins verbeuse de déclarer les é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 devrez synchroniser l'ordre des noms et des valeurs, ce qui est plus facile à faire si le nom et la valeur 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))
}
}
vient de découvrir enumeratum . c'est assez étonnant et tout aussi étonnant ce n'est pas plus connu!
Après avoir fait des recherches approfondies sur toutes les options autour des "énumérations" dans Scala, j'ai posté un aperçu beaucoup plus complet de ce domaine sur un autre thread StackOverflow . Il comprend une solution au modèle "trait scellé + objet cas" où j'ai résolu le problème de commande d'initialisation de classe / objet JVM.
À 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
Cet exemple de leur documentation 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)