¿Cómo modelar tipos de enum seguros de tipo?

Scala no tiene enum tipo seguro como Java. Dado un conjunto de constantes relacionadas, ¿cuál sería la mejor manera en Scala para representar esas constantes?

http://www.scala-lang.org/docu/files/api/scala/Enumeration.html

Ejemplo de uso

  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 } 

Debo decir que el ejemplo copiado de la documentación de Scala por skaffman de arriba es de utilidad limitada en la práctica (también podría usar case object ).

Para obtener algo que se asemeje más a un Enum Java (es decir, con métodos toString y valueOf sensibles, tal vez está persistiendo los valores enum a una base de datos), necesita modificarlo un poco. Si hubieras usado el código de skaffman :

 WeekDay.valueOf("Sun") //returns None WeekDay.Tue.toString //returns Weekday(2) 

Considerando que utilizando la siguiente statement:

 object WeekDay extends Enumeration { type WeekDay = Value val Mon = Value("Mon") val Tue = Value("Tue") ... etc } 

Obtienes resultados más sensatos:

 WeekDay.valueOf("Sun") //returns Some(Sun) WeekDay.Tue.toString //returns Tue 

Hay muchas formas de hacerlo.

1) Usa símbolos. Sin embargo, no le proporcionará ningún tipo de seguridad, además de no aceptar símbolos que no sean simbolos cuando se espera un símbolo. Solo lo estoy mencionando aquí para completarlo. Aquí hay un ejemplo de uso:

 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) Uso de la Enumeration clases:

 object Dimension extends Enumeration { type Dimension = Value val Row, Column = Value } 

o, si necesita serializarlo o mostrarlo:

 object Dimension extends Enumeration("Row", "Column") { type Dimension = Value val Row, Column = Value } 

Esto se puede usar así:

 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) :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 / 

Lamentablemente, no garantiza que se tengan en cuenta todas las coincidencias. Si olvidé poner Row o Column en el partido, el comstackdor de Scala no me habría avisado. Entonces me da seguridad de algún tipo, pero no tanto como se puede ganar.

3) objetos del caso:

 sealed abstract class Dimension case object Row extends Dimension case object Column extends Dimension 

Ahora, si dejo fuera un caso en un match , el comstackdor me advertirá:

 MatrixInt.scala:70: warning: match is not exhaustive! missing combination Column what match { ^ one warning found 

Se usa más o menos de la misma manera, y ni siquiera necesita una 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 / 

Puede que se pregunte, entonces, por qué utilizar una enumeración en lugar de objetos de casos. Como cuestión de hecho, los objetos de caso tienen ventajas muchas veces, como aquí. La clase Enumeration, sin embargo, tiene muchos métodos de recostackción, como elementos (iterador en Scala 2.8), que devuelve un Iterador, mapa, mapa plano, filtro, etc.

Esta respuesta es esencialmente una parte seleccionada de este artículo en mi blog.

Una forma un poco menos detallada de declarar enumeraciones con nombre:

 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 

Por supuesto, el problema aquí es que deberá mantener el orden de los nombres y vals sincronizados, lo que es más fácil si el nombre y el valor se declaran en la misma línea.

Puede usar una clase abstracta sellada en lugar de la enumeración, por ejemplo:

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

acaba de descubrir enumeratum . es bastante sorprendente e igualmente sorprendente, ¡no es más conocido!

Después de hacer una extensa investigación sobre todas las opciones en torno a “enumeraciones” en Scala, publiqué una descripción mucho más completa de este dominio en otro hilo de StackOverflow . Incluye una solución al patrón “objeto sellado + objeto de caso” donde he resuelto el problema de ordenación de la clase / objeto JVM.

Dotty (Scala 3) tendrá enums nativos compatibles. Mira aquí y aquí .