web-dev-qa-db-fra.com

Comment modéliser des types enum-safe?

Scala ne possède pas enumsé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?

311
Jesper

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
  }
186
skaffman

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 objects).

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
376
oxbow_lakes

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.

98
Daniel C. Sobral

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.

52
Walter Chang

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))
  }

}
17
ron

je viens de découvrir enumeratum . c'est assez incroyable et tout aussi incroyable, ce n'est pas plus connu!

7
practechal

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.

2
chaotic3quilibrium

Dotty (Scala 3) aura un enum natif supporté. Vérifiez ici et ici .

1
zeronone

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)
1
Dmitriy Kuzkin