Scala no tiene s de tipo seguro enum
como Java. Dado un conjunto de constantes relacionadas, ¿cuál sería la mejor manera en Scala para representar esas constantes?
Scala no tiene s de tipo seguro enum
como Java. Dado un conjunto de constantes relacionadas, ¿cuál sería la mejor manera en Scala para representar esas constantes?
Respuestas:
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 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étodos toString
y valueOf
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 :
WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString //returns Weekday(2)
Mientras que utilizando la siguiente declaración:
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon = Value("Mon")
val Tue = Value("Tue")
... etc
}
Obtienes resultados más sensibles:
WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString //returns Tue
valueOf
El reemplazo de @macias es withName
, que no devuelve una Opción, y arroja un NSE si no hay coincidencia. ¡Que!
Map[Weekday.Weekday, Long]
y agregar un valor, le digo Mon
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:
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 clase Enumeration
:
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)
<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 /
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:
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 casi de la misma manera, y ni siquiera necesita 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 /
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.
Symbol
instancias no pueden tener espacios o caracteres especiales. La mayoría de las personas cuando se encuentran por primera vez con la Symbol
clase probablemente piensan eso, pero en realidad es incorrecto. Symbol("foo !% bar -* baz")
compila y funciona perfectamente bien. En otras palabras, puede crear perfectamente Symbol
instancias que envuelvan cualquier cadena (simplemente no puede hacerlo con el azúcar sintáctico de "coma único"). Lo único que Symbol
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 un Symbol
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 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én Symbol
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:
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 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.
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))
}
}
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.
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
import enumeratum._
sealed trait Greeting extends EnumEntry
object Greeting extends Enum[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
case object Hello extends Greeting
case object GoodBye extends Greeting
case object Hi extends Greeting
case object Bye extends Greeting
}
// Object Greeting has a `withName(name: String)` method
Greeting.withName("Hello")
// => res0: Greeting = Hello
Greeting.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 insensitively
Greeting.withNameInsensitive("HeLLo")
// => res3: Greeting = Hello
Greeting.withNameInsensitiveOption("HeLLo")
// => res4: Option[Greeting] = Some(Hello)
// Uppercase-only strings may also be used
Greeting.withNameUppercaseOnly("HELLO")
// => res5: Greeting = Hello
Greeting.withNameUppercaseOnlyOption("HeLLo")
// => res6: Option[Greeting] = None
// Similarly, lowercase-only strings may also be used
Greeting.withNameLowercaseOnly("hello")
// => res7: Greeting = Hello
Greeting.withNameLowercaseOnlyOption("hello")
// => res8: Option[Greeting] = Some(Hello)