scala enums

scala - ¿Cómo modelar tipos de enumeración de tipo seguro?



enums (8)

Acabo de descubrir enumeratum . Es bastante asombroso e igualmente asombroso, ¡no es más conocido!

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


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

Para obtener algo que se parezca más a un Enum Java (es decir, con métodos sensibles a la toString y al valor de valueOf (tal vez esté persistiendo los valores de la enumeración en una base de datos), debe modificarlo un poco. Si has usado el código de skaffman :

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

Considerando que utiliza la siguiente declaración:

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


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


Dotty (Scala 3) tendrá enums nativos compatibles. Consulta here y here .


Hay muchas maneras de hacerlo.

1) Utilizar símbolos. Sin embargo, no le brindará ningún tipo de seguridad, además de no aceptar los no símbolos donde se espera un símbolo. Solo lo menciono aquí para completar. 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) Usando la Enumeration clase:

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 utilizar de esta manera:

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 /

Desafortunadamente, no garantiza que todas las coincidencias se tengan en cuenta. Si olvidara poner una fila o una columna en la partida, el compilador de Scala no me habría advertido. Así que me da algún tipo de seguridad, pero no tanto como se puede obtener.

3) Objetos de la caja:

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

Ahora, si dejo un caso en un match , el compilador me avisará:

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 /

Podría preguntarse, entonces, por qué usar una enumeración en lugar de objetos de caso. De hecho, los objetos de caso tienen ventajas muchas veces, como aquí. La clase de enumeración, sin embargo, tiene muchos métodos de colección, como los elementos (iterador en Scala 2.8), que devuelve un iterador, un mapa, un mapa plano, un filtro, etc.

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


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


Una forma ligeramente menos detallada de declarar enumeraciones nombradas:

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 la ordenación de los nombres y valores sincronizados, lo que es más fácil de hacer si el nombre y el valor de valor se declaran en la misma línea.


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 }