2009-08-24 9 views
299

Scala no tiene caja fuerte enum como Java. Dado un conjunto de constantes relacionadas, ¿cuál sería la mejor manera en Scala para representar esas constantes?¿Cómo modelar tipos de enum seguros de tipo?

+2

¿Por qué no utilizar Java enum? Esta es una de las pocas cosas que aún prefiero usar Java simple. – Max

+1

He escrito una pequeña descripción general sobre la enumeración scala y sus alternativas, puede que le resulte útil: pedrorijo.com/blog/scala-enums/ – pedrorijo91

Respuesta

179

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

ejemplo, el 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 
    } 
+2

En serio, no se debe utilizar la aplicación. NO fue arreglado; se introdujo una nueva clase, App, que no tiene los problemas que menciona Schildmeijer. Entonces, "object foo extends App {...}" Y tienes acceso inmediato a los argumentos de la línea de comandos a través de la variable args. – AmigoNico

+0

scala.Enumeration (que es lo que está utilizando en el ejemplo de código "objeto WeekDay" anterior) no ofrece una concordancia exhaustiva de patrones. Investigué todos los diferentes patrones de enumeración que se usan actualmente en Scala y los proporciono y resumen de ellos en esta respuesta de StackOverflow (incluido un nuevo patrón que ofrece lo mejor de Scala.Enumeration y el patrón de "rasgo sellado + objeto de caso": http: //stackoverflow.com/a/25923651/501113 – chaotic3quilibrium

372

hay que decir que el ejemplo copia de la documentación Scala por skaffman anterior es de utilidad limitada en la práctica (que también podría utilizar case object s).

Con el fin de obtener algo más de cerca se asemeja a un Java Enum (es decir, con sensibles toString y métodos valueOf - tal vez usted está persistiendo los valores de enumeración a una base de datos) tiene que modificar un poco. Si se hubiera usado skaffman 's código:

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

Mientras que, mediante esta declaración:

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

Se obtienen resultados más sensibles:

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

Por cierto, el método valueOf ahora está muerto :-( – greenoldman

+35

El reemplazo de @macias 'valueOf' es' withName', que no devuelve una opción, y arroja un NSE si hay no coincide. ¡Qué! – Bluu

+6

@Bluu Puede agregar el valor de usted mismo: def valorDe (nombre: Cadena) = WeekDay.values.find (_. toString == nombre) para tener una Opción – centr

52

una manera un poco menos detallado 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 en sincronía, lo que es más fácil si el nombre y el valor se declaran en la misma línea.

+10

Esto se ve más limpio a primera vista, pero tiene la desventaja de requerir que el mantenedor mantenga sincronizadas las dos listas. Para el ejemplo de los días de la semana, no parece probable. Pero, en general, podría insertarse un nuevo valor, o uno eliminado, y las dos listas podrían no estar sincronizadas, en cuyo caso podrían introducirse errores sutiles. –

+1

Según el comentario anterior, el riesgo es que las dos listas diferentes puedan silenciarse de forma silenciosa.Si bien no es un problema para su pequeño ejemplo actual, si hay muchos más miembros (como de decenas a cientos), las probabilidades de que las dos listas se desconecten silenciosamente son sustancialmente más altas. Además Scala.Enumeration no puede beneficiarse de las advertencias/errores de concordancia del patrón exhaustivo del tiempo de compilación de Scala. Creé una respuesta de StackOverflow que contiene una solución que realiza una verificación de tiempo de ejecución para garantizar que las dos listas permanezcan sincronizadas: http://stackoverflow.com/a/25923651/501113 – chaotic3quilibrium

96

Hay muchas maneras de hacerlo.

1) Use 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í está 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) El uso de la clase Enumeration:

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

o, si es necesario serializar o mostrarlo:

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

Esto puede ser usado como esto :

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/

Unfortu nately, no garantiza que se tengan en cuenta todas las coincidencias. Si olvidé poner Row o Column en el partido, el compilador de Scala no me habría avisado. Entonces me da algo de seguridad de tipo, pero no tanto como se puede obtener.objetos

3) Caso:

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

Ahora, si dejo a cabo un caso en una match, el compilador avisarme:

MatrixInt.scala:70: warning: match is not exhaustive! 
missing combination   Column 

    what match { 
    ^
one warning found 

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

Usted podría preguntarse, entonces, ¿por qué nos nunca e 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 colección, como elementos (iterador en Scala 2.8), que devuelve un iterador, mapa, flatMap, filtro, etc.

Esta respuesta es esencialmente una selección de piezas de this article en mi blog .

16

Se puede utilizar 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)) 
    } 

} 
+0

El rasgo sellado con objetos de caso también es una posibilidad. – Ashalynd

+2

El patrón "objeto sellado + objetos del caso" tiene problemas que detallo en una respuesta de StackOverflow. Sin embargo, descubrí cómo resolver todos los problemas relacionados con este patrón que también está cubierto en el hilo: http://stackoverflow.com/a/25923651/501113 – chaotic3quilibrium

2

Después de hacer una investigación exhaustiva sobre todas las opciones en torno a "enumeraciones" en la Scala, he publicado una visión mucho más completa de este dominio en otro StackOverflow thread. 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.

6

recientemente descubierto enumeratum. es bastante sorprendente e igualmente sorprendente, ¡no es más conocido!