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