¿Cómo modelar tipos de enumeración de tipo seguro?
311
Scala no tiene s de tipo seguro enumcomo Java. Dado un conjunto de constantes relacionadas, ¿cuál sería la mejor manera en Scala para representar esas constantes?
En serio, la aplicación no debe ser utilizada. NO fue arreglado; Se introdujo una nueva clase, App, que no tiene los problemas mencionados por Schildmeijer. Así que "object foo extiende la aplicación {...}" Y tiene acceso inmediato a los argumentos de la línea de comandos a través de la variable args.
AmigoNico
scala.Enumeration (que es lo que está utilizando en su ejemplo de código "object WeekDay" anterior) no ofrece una concordancia exhaustiva de patrones. He investigado todos los diferentes patrones de enumeración que se usan actualmente en Scala y les doy una descripción general de ellos en esta respuesta de StackOverflow (incluido un nuevo patrón que ofrece lo mejor de ambos scala.Enumeration y el patrón "rasgo sellado + objeto de caso": stackoverflow. com / a / 25923651/501113
chaotic3quilibrium
377
Debo decir que el ejemplo copiado de la documentación de Scala por skaffman anterior es de utilidad limitada en la práctica (también podría usar case objects).
Para obtener algo que se parezca más a un Java Enum(es decir, con métodos toStringy valueOfmétodos razonables , quizás persista los valores de enumeración en una base de datos), debe modificarlo un poco. Si hubiera usado el código de skaffman :
Por cierto. El método valueOf ahora está muerto :-(
greenoldman
36
valueOfEl reemplazo de @macias es withName, que no devuelve una Opción, y arroja un NSE si no hay coincidencia. ¡Que!
Bluu
66
@Bluu Puede agregar valorDe usted mismo: def valueOf (name: String) = WeekDay.values.find (_. ToString == name) para tener una Opción
centr
@centr Cuando intento crear Map[Weekday.Weekday, Long]y agregar un valor, le digo Monque el compilador arroja un error de tipo no válido. Día de la semana esperado. ¿Valor encontrado el día de la semana? ¿Por qué pasó esto?
Sohaib
@Sohaib Debería ser Mapa [Weekday.Value, Long].
centr
99
Hay muchas formas de hacerlo.
1) Usa símbolos. Sin embargo, no le dará ningún tipo de seguridad, aparte de no aceptar no símbolos donde se espera un símbolo. Solo lo estoy mencionando 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 _ =>thrownewIllegalArgumentException}// At REPL:
scala>val a = unitMatrixInt(3)
a: teste7.MatrixInt=/100\|010|\001/
scala> a('row,1)= a.row(0)
res41: teste7.MatrixInt=/100\|100|\001/
scala> a('column,2)= a.row(0)
res42: teste7.MatrixInt=/101\|010|\000/
def update(what:Dimension, where:Int, newValue:Array[Int]):MatrixInt=
what match{caseRow=> replaceRow(where, newValue)caseColumn=> 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=/100\|010|\010/
scala>importDimension._
importDimension._
scala> a(Row,2)= a.row(1)
res2: teste.MatrixInt=/100\|010|\010/
Lamentablemente, 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 lo habría advertido. Por lo tanto, me da cierto tipo de seguridad, pero no tanto como se puede obtener.
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í. Sin embargo, la clase Enumeration 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 parte seleccionada de este artículo en mi blog.
"... no se aceptan no símbolos donde se espera un símbolo"> Supongo que quiere decir que las Symbolinstancias no pueden tener espacios o caracteres especiales. La mayoría de las personas cuando se encuentran por primera vez con la Symbolclase probablemente piensan eso, pero en realidad es incorrecto. Symbol("foo !% bar -* baz")compila y funciona perfectamente bien. En otras palabras, puede crear perfectamente Symbolinstancias que envuelvan cualquier cadena (simplemente no puede hacerlo con el azúcar sintáctico de "coma único"). Lo único que Symbolgarantiza es la singularidad de cualquier símbolo dado, lo que lo hace marginalmente más rápido para comparar y combinar.
Régis Jean-Gilles
@ RégisJean-Gilles No, quiero decir que no puede pasar un String, por ejemplo, como argumento a un Symbolparámetro.
Daniel C. Sobral
Sí, entendí esa parte, pero es un punto discutible si lo reemplazas Stringcon otra clase que básicamente es un envoltorio alrededor de una cadena y se puede convertir libremente en ambas direcciones (como es el caso Symbol). Supongo que a eso se refería cuando decía "No le dará ningún tipo de seguridad", simplemente no estaba muy claro dado que OP solicitó explícitamente soluciones de tipo seguro. No estaba segura de si en el momento de escribir que sabía que no sólo no es un tipo seguro, porque esos no son enumeraciones en absoluto, sino tambiénSymbol s ni siquiera garantía de que el argumento pasado no tendrá caracteres especiales.
Régis Jean-Gilles
1
Para elaborar, cuando dices "no aceptar no símbolos donde se espera un símbolo", puede leerse como "no aceptar valores que no son instancias de Symbol" (lo cual es obviamente cierto) o "no aceptar valores que no son identificador-como llanura cuerdas, también conocido como 'símbolos'"(que no es cierto, y es una idea errónea de que casi nadie tiene la primera vez que nos encontramos con símbolos Scala, debido al hecho de que el primer encuentro es a pesar de la especial 'foode notación, que hace oponen cadenas sin identificador). Este es un concepto erróneo que quería disipar para cualquier futuro lector.
Régis Jean-Gilles
@ RégisJean-Gilles Me refería al primero, el que obviamente es cierto. Quiero decir, obviamente es cierto para cualquiera que esté acostumbrado a la escritura estática. En aquel entonces hubo mucha discusión sobre los méritos relativos de la escritura estática y "dinámica", y mucha gente interesada en Scala provenía de un entorno de escritura dinámica, así que pensé que no pasó sin decirlo. Ni siquiera pensaría en hacer ese comentario hoy en día. Personalmente, creo que el Símbolo de Scala es feo y redundante, y nunca lo use. Estoy votando tu último comentario, ya que es un buen punto.
Daniel C. Sobral
52
Una forma un poco menos detallada de declarar enumeraciones nombradas:
Por supuesto, el problema aquí es que necesitará mantener el orden de los nombres y valores sincronizados, lo que es más fácil de hacer si se declaran nombre y valor en la misma línea.
Esto parece más limpio a primera vista, pero tiene la desventaja de requerir que el responsable mantenga sincronizado el orden de ambas listas. Para el ejemplo de los días de la semana, no parece probable. Pero, en general, se puede insertar un nuevo valor, o se puede eliminar uno y las dos listas pueden estar fuera de sincronización, en cuyo caso, se pueden introducir errores sutiles.
Brent Faust
1
Según el comentario anterior, el riesgo es que las dos listas diferentes pueden sincronizarse silenciosamente. Si bien no es un problema para su pequeño ejemplo actual, si hay muchos más miembros (como decenas a cientos), las probabilidades de que las dos listas se desincronicen en silencio es sustancialmente mayor. También scala. La enumeración no puede beneficiarse de las advertencias / errores exhaustivos de coincidencia de patrones de tiempo de compilación de Scala. He creado una respuesta StackOverflow que contiene una solución que realiza una comprobación de tiempo de ejecución para garantizar que las dos listas permanezcan sincronizadas: stackoverflow.com/a/25923651/501113
chaotic3quilibrium
17
Puede usar una clase abstracta sellada en lugar de la enumeración, por ejemplo:
El rasgo sellado con objetos de caja también es una posibilidad.
Ashalynd
2
El patrón "rasgo cerrado + objetos de 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: stackoverflow.com/a/25923651/501113
chaotic3quilibrium
7
acabo 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 las "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 "rasgo sellado + objeto de caso" en el que he resuelto el problema de ordenación de inicialización de clase / objeto JVM.
El proyecto es realmente bueno con ejemplos y documentación.
Solo este ejemplo de sus documentos debería hacerte interesado en
import enumeratum._
sealedtraitGreetingextendsEnumEntryobjectGreetingextendsEnum[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
caseobjectHelloextendsGreetingcaseobjectGoodByeextendsGreetingcaseobjectHiextendsGreetingcaseobjectByeextendsGreeting}// Object Greeting has a `withName(name: String)` methodGreeting.withName("Hello")// => res0: Greeting = HelloGreeting.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 insensitivelyGreeting.withNameInsensitive("HeLLo")// => res3: Greeting = HelloGreeting.withNameInsensitiveOption("HeLLo")// => res4: Option[Greeting] = Some(Hello)// Uppercase-only strings may also be usedGreeting.withNameUppercaseOnly("HELLO")// => res5: Greeting = HelloGreeting.withNameUppercaseOnlyOption("HeLLo")// => res6: Option[Greeting] = None// Similarly, lowercase-only strings may also be usedGreeting.withNameLowercaseOnly("hello")// => res7: Greeting = HelloGreeting.withNameLowercaseOnlyOption("hello")// => res8: Option[Greeting] = Some(Hello)
Respuestas:
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
Ejemplo de uso
fuente
Debo decir que el ejemplo copiado de la documentación de Scala por skaffman anterior es de utilidad limitada en la práctica (también podría usar
case object
s).Para obtener algo que se parezca más a un Java
Enum
(es decir, con métodostoString
yvalueOf
métodos razonables , quizás persista los valores de enumeración en una base de datos), debe modificarlo un poco. Si hubiera usado el código de skaffman :Mientras que utilizando la siguiente declaración:
Obtienes resultados más sensibles:
fuente
valueOf
El reemplazo de @macias eswithName
, que no devuelve una Opción, y arroja un NSE si no hay coincidencia. ¡Que!Map[Weekday.Weekday, Long]
y agregar un valor, le digoMon
que el compilador arroja un error de tipo no válido. Día de la semana esperado. ¿Valor encontrado el día de la semana? ¿Por qué pasó esto?Hay muchas formas de hacerlo.
1) Usa símbolos. Sin embargo, no le dará ningún tipo de seguridad, aparte de no aceptar no símbolos donde se espera un símbolo. Solo lo estoy mencionando aquí para completar. Aquí hay un ejemplo de uso:
2) Usando la clase
Enumeration
:o, si necesita serializarlo o mostrarlo:
Esto se puede usar así:
Lamentablemente, 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 lo habría advertido. Por lo tanto, me da cierto tipo de seguridad, pero no tanto como se puede obtener.
3) Objetos de caso:
Ahora, si dejo un caso en un
match
, el compilador me avisará:Se usa casi de la misma manera, y ni siquiera necesita un
import
: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í. Sin embargo, la clase Enumeration 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 parte seleccionada de este artículo en mi blog.
fuente
Symbol
instancias no pueden tener espacios o caracteres especiales. La mayoría de las personas cuando se encuentran por primera vez con laSymbol
clase probablemente piensan eso, pero en realidad es incorrecto.Symbol("foo !% bar -* baz")
compila y funciona perfectamente bien. En otras palabras, puede crear perfectamenteSymbol
instancias que envuelvan cualquier cadena (simplemente no puede hacerlo con el azúcar sintáctico de "coma único"). Lo único queSymbol
garantiza es la singularidad de cualquier símbolo dado, lo que lo hace marginalmente más rápido para comparar y combinar.String
, por ejemplo, como argumento a unSymbol
parámetro.String
con otra clase que básicamente es un envoltorio alrededor de una cadena y se puede convertir libremente en ambas direcciones (como es el casoSymbol
). Supongo que a eso se refería cuando decía "No le dará ningún tipo de seguridad", simplemente no estaba muy claro dado que OP solicitó explícitamente soluciones de tipo seguro. No estaba segura de si en el momento de escribir que sabía que no sólo no es un tipo seguro, porque esos no son enumeraciones en absoluto, sino tambiénSymbol
s ni siquiera garantía de que el argumento pasado no tendrá caracteres especiales.'foo
de notación, que hace oponen cadenas sin identificador). Este es un concepto erróneo que quería disipar para cualquier futuro lector.Una forma un poco menos detallada de declarar enumeraciones nombradas:
Por supuesto, el problema aquí es que necesitará mantener el orden de los nombres y valores sincronizados, lo que es más fácil de hacer si se declaran nombre y valor en la misma línea.
fuente
Puede usar una clase abstracta sellada en lugar de la enumeración, por ejemplo:
fuente
acabo de descubrir enumeratum . ¡Es bastante sorprendente e igualmente sorprendente, no es más conocido!
fuente
Después de hacer una extensa investigación 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 StackOverflow . Incluye una solución al patrón "rasgo sellado + objeto de caso" en el que he resuelto el problema de ordenación de inicialización de clase / objeto JVM.
fuente
Dotty (Scala 3) tendrá enums nativos compatibles. Mira aquí y aquí .
fuente
En Scala es muy cómodo con https://github.com/lloydmeta/enumeratum
El proyecto es realmente bueno con ejemplos y documentación.
Solo este ejemplo de sus documentos debería hacerte interesado en
fuente