Objetos de caso vs Enumeraciones en Scala

¿Existen pautas de mejores prácticas sobre cuándo usar clases de casos (u objetos de casos) frente a extender la enumeración en Scala?

Parecen ofrecer algunos de los mismos beneficios.

Una gran diferencia es que la Enumeration viene con soporte para crear instancias de alguna cadena de name . Por ejemplo:

 object Currency extends Enumeration { val GBP = Value("GBP") val EUR = Value("EUR") //etc. } 

Entonces puedes hacer:

 val ccy = Currency.withName("EUR") 

Esto es útil cuando desea persistir enumeraciones (por ejemplo, en una base de datos) o crearlas a partir de datos que residen en archivos. Sin embargo, en general me parece que las enumeraciones son un poco torpes en Scala y tengo la sensación de un complemento incómodo, por lo que ahora tiendo a usar case object . Un case object es más flexible que una enumeración:

 sealed trait Currency { def name: String } case object EUR extends Currency { val name = "EUR" } //etc. case class UnknownCurrency(name: String) extends Currency 

Así que ahora tengo la ventaja de …

 trade.ccy match { case EUR => case UnknownCurrency(code) => } 

Como señaló @ chaotic3quilibrium (con algunas correcciones para facilitar la lectura):

Con respecto al patrón de “Moneda desconocida (código)”, hay otras formas de evitar encontrar una cadena de código de moneda que “romper” la naturaleza de conjunto cerrado del tipo de Currency . UnknownCurrency Currency UnknownCurrency de tipo Currency ahora puede colarse en otras partes de una API.

Es aconsejable llevar ese caso fuera de Enumeration y hacer que el cliente maneje un tipo de Option[Currency] que indique claramente que realmente hay un problema coincidente y “alentar” al usuario de la API a que lo resuelva él mismo.

Para dar seguimiento a las otras respuestas aquí, los principales inconvenientes de los case object del case object s sobre Enumeration s son:

  1. No puede iterar sobre todas las instancias de la “enumeración” . Este es ciertamente el caso, pero en la práctica me resulta extremadamente raro que esto sea necesario.

  2. No se puede crear una instancia fácilmente de un valor persistente . Esto también es cierto, pero, excepto en el caso de grandes enumeraciones (por ejemplo, todas las monedas), esto no representa una gran sobrecarga.

Los objetos de caso ya devuelven sus nombres a sus métodos toString, por lo que no es necesario pasarlos por separado. Aquí hay una versión similar a jho (métodos de conveniencia omitidos por brevedad):

 trait Enum[A] { trait Value { self: A => } val values: List[A] } sealed trait Currency extends Currency.Value object Currency extends Enum[Currency] { case object EUR extends Currency case object GBP extends Currency val values = List(EUR, GBP) } 

Los objetos son flojos; usando vals en su lugar podemos soltar la lista pero tenemos que repetir el nombre:

 trait Enum[A <: {def name: String}] { trait Value { self: A => _values :+= this } private var _values = List.empty[A] def values = _values } sealed abstract class Currency(val name: String) extends Currency.Value object Currency extends Enum[Currency] { val EUR = new Currency("EUR") {} val GBP = new Currency("GBP") {} } 

Si no te importa hacer trampa, puedes cargar tus valores de enumeración con la API de reflexión o algo así como Google Reflections. Los objetos de caso no perezosos le dan la syntax más limpia:

 trait Enum[A] { trait Value { self: A => _values :+= this } private var _values = List.empty[A] def values = _values } sealed trait Currency extends Currency.Value object Currency extends Enum[Currency] { case object EUR extends Currency case object GBP extends Currency } 

Agradable y limpio, con todas las ventajas de las clases de casos y las enumeraciones de Java. Personalmente, defino los valores de enumeración fuera del objeto para que coincida mejor con el código de Scala idiomático:

 object Currency extends Enum[Currency] sealed trait Currency extends Currency.Value case object EUR extends Currency case object GBP extends Currency 

ACTUALIZACIÓN: Se ha creado una nueva solución basada en macro que es muy superior a la solución que describo a continuación. Recomiendo usar esta nueva solución basada en macro . Y parece que los planes para Dotty convertirán este estilo de la solución enum en parte del lenguaje. Whoohoo!

Resumen:
Hay tres patrones básicos para intentar reproducir el Java Enum dentro de un proyecto de Scala. Dos de los tres patrones; directamente utilizando Java Enum y scala.Enumeration , no son capaces de habilitar la concordancia exhaustiva de patrones de Scala. Y el tercero; “rasgo sellado + objeto de caso”, sí … pero tiene complicaciones de inicialización de clase / objeto JVM que dan como resultado una generación de índice ordinal incoherente.

Creé una solución con dos clases; Enumeración y EnumeraciónDecorada , ubicada en este Gist . No publiqué el código en este hilo porque el archivo para Enumeración era bastante grande (+400 líneas, contiene muchos comentarios que explican el contexto de implementación).

Detalles:
La pregunta que estás haciendo es bastante general; “… cuándo usar objects clases de case frente a extender [scala.]Enumeration “. Y resulta que hay MUCHAS respuestas posibles, cada respuesta depende de las sutilezas de los requisitos específicos del proyecto que tenga. La respuesta puede reducirse a tres patrones básicos.

Para comenzar, asegurémonos de trabajar desde la misma idea básica de lo que es una enumeración. Definamos una enumeración principalmente en términos del Enum proporcionado a partir de Java 5 (1.5) :

  1. Contiene un conjunto cerrado naturalmente ordenado de miembros nombrados
    1. Hay un número fijo de miembros
    2. Los miembros están naturalmente ordenados y explícitamente indexados
      • A diferencia de ser ordenado en función de algunos criterios deseables de los miembros inactivos
    3. Cada miembro tiene un nombre único dentro del conjunto total de todos los miembros
  2. Todos los miembros se pueden iterar fácilmente según sus índices
  3. Un miembro se puede recuperar con su nombre (sensible a mayúsculas y minúsculas)
    1. Sería bastante bueno si un miembro también se puede recuperar con su nombre insensible a mayúsculas y minúsculas
  4. Un miembro puede ser recuperado con su índice
  5. Los miembros pueden usar la serialización de manera fácil, transparente y eficiente
  6. Los miembros pueden ampliarse fácilmente para mantener datos de singleton-ness asociados adicionales
  7. Pensando más allá de Enum de Java, sería bueno poder aprovechar explícitamente la comprobación de exhaustividad de concordancia de patrones de Scala para una enumeración

A continuación, veamos versiones reducidas de los tres patrones de solución más comunes publicados:

A) En realidad, directamente usando el patrón Java Enum (en un proyecto mixto Scala / Java):

 public enum ChessPiece { KING('K', 0) , QUEEN('Q', 9) , BISHOP('B', 3) , KNIGHT('N', 3) , ROOK('R', 5) , PAWN('P', 1) ; private char character; private int pointValue; private ChessPiece(char character, int pointValue) { this.character = character; this.pointValue = pointValue; } public int getCharacter() { return character; } public int getPointValue() { return pointValue; } } 

Los siguientes elementos de la definición de enumeración no están disponibles:

  1. 3.1 – Sería bastante agradable si un miembro también se puede recuperar con su nombre insensible a mayúsculas y minúsculas
  2. 7 – Pensando más allá de Enum de Java, sería bueno poder aprovechar explícitamente la comprobación de exhaustividad de concordancia de patrones de Scala para una enumeración

Para mis proyectos actuales, no tengo el beneficio de tomar riesgos en el camino del proyecto mixto Scala / Java. E incluso si pudiera elegir hacer un proyecto mixto, el ítem 7 es crítico para permitirme detectar problemas de tiempo de comstackción si / cuando agrego / elimino miembros de enumeración, o estoy escribiendo algún código nuevo para tratar con miembros de enumeración existentes.

B) Usando el patrón ” sealed trait + case objects “:

 sealed trait ChessPiece {def character: Char; def pointValue: Int} object ChessPiece { case object KING extends ChessPiece {val character = 'K'; val pointValue = 0} case object QUEEN extends ChessPiece {val character = 'Q'; val pointValue = 9} case object BISHOP extends ChessPiece {val character = 'B'; val pointValue = 3} case object KNIGHT extends ChessPiece {val character = 'N'; val pointValue = 3} case object ROOK extends ChessPiece {val character = 'R'; val pointValue = 5} case object PAWN extends ChessPiece {val character = 'P'; val pointValue = 1} } 

Los siguientes elementos de la definición de enumeración no están disponibles:

  1. 1.2 – Los miembros son ordenados naturalmente e indexados explícitamente
  2. 2 – Todos los miembros pueden iterarse fácilmente según sus índices
  3. 3 – Un miembro puede ser recuperado con su nombre (sensible a mayúsculas y minúsculas)
  4. 3.1 – Sería bastante agradable si un miembro también se puede recuperar con su nombre insensible a mayúsculas y minúsculas
  5. 4 – Un miembro puede ser recuperado con su índice

Es discutible que realmente cumple con los ítems de definición de enumeración 5 y 6. Para 5, es difícil afirmar que es eficiente. Para 6, no es realmente fácil de extender para mantener datos de singleton-ness asociados adicionales.

C) Uso del patrón scala.Enumeration (inspirado en esta respuesta de StackOverflow ):

 object ChessPiece extends Enumeration { val KING = ChessPieceVal('K', 0) val QUEEN = ChessPieceVal('Q', 9) val BISHOP = ChessPieceVal('B', 3) val KNIGHT = ChessPieceVal('N', 3) val ROOK = ChessPieceVal('R', 5) val PAWN = ChessPieceVal('P', 1) protected case class ChessPieceVal(character: Char, pointValue: Int) extends super.Val() implicit def convert(value: Value) = value.asInstanceOf[ChessPieceVal] } 

Los siguientes elementos de la definición de enumeración no están disponibles (pasa a ser idéntico a la lista para usar directamente Java Enum):

  1. 3.1 – Sería bastante agradable si un miembro también se puede recuperar con su nombre insensible a mayúsculas y minúsculas
  2. 7 – Pensando más allá de Enum de Java, sería bueno poder aprovechar explícitamente la comprobación de exhaustividad de concordancia de patrones de Scala para una enumeración

Nuevamente para mis proyectos actuales, el ítem 7 es crítico para permitirme detectar problemas de tiempo de comstackción si / cuando agrego / elimino miembros de enumeración, o estoy escribiendo algún código nuevo para tratar con miembros de enumeración existentes.


Por lo tanto, dada la definición anterior de una enumeración, ninguna de las tres soluciones anteriores funciona, ya que no proporcionan todo lo descrito en la definición de enumeración anterior:

  1. Java Enum directamente en un proyecto mixto Scala / Java
  2. “objetos sellados de rasgo + caso”
  3. scala.Enumeration

Cada una de estas soluciones puede eventualmente reelaborarse / expandirse / refactorizarse para tratar de cubrir algunos de los requisitos que faltan. Sin embargo, ni las soluciones Java Enum ni scala.Enumeration pueden ampliarse lo suficiente como para proporcionar el elemento 7. Y para mis propios proyectos, este es uno de los valores más convincentes de usar un tipo cerrado dentro de Scala. Yo prefiero las advertencias / errores de tiempo de comstackción para indicar que tengo una brecha / problema en mi código en lugar de tener que extraerlo de una excepción / falla de tiempo de ejecución de producción.


En ese sentido, me puse a trabajar con la ruta del case object del case object para ver si podía producir una solución que cubría toda la definición de enumeración anterior. El primer desafío fue avanzar a través del núcleo del problema de inicialización de clase / objeto de JVM (cubierto en detalle en esta publicación de StackOverflow ). Y finalmente pude encontrar una solución.

Como mi solución son dos rasgos; Enumeration y EnumerationDecorated , y dado que el rasgo Enumeration tiene más de 400 líneas de longitud (muchos comentarios que explican el contexto), estoy renunciando a pegarlo en este hilo (lo que haría que se extendiera por la página considerablemente). Para más detalles, salte directamente al Gist .

Esto es lo que la solución parece tener el mismo uso de la misma idea de datos que la anterior (la versión completamente comentada está disponible aquí ) y se implementa en EnumerationDecorated .

 import scala.reflect.runtime.universe.{TypeTag,typeTag} import org.public_domain.scala.utils.EnumerationDecorated object ChessPiecesEnhancedDecorated extends EnumerationDecorated { case object KING extends Member case object QUEEN extends Member case object BISHOP extends Member case object KNIGHT extends Member case object ROOK extends Member case object PAWN extends Member val decorationOrderedSet: List[Decoration] = List( Decoration(KING, 'K', 0) , Decoration(QUEEN, 'Q', 9) , Decoration(BISHOP, 'B', 3) , Decoration(KNIGHT, 'N', 3) , Decoration(ROOK, 'R', 5) , Decoration(PAWN, 'P', 1) ) final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase { val description: String = member.name.toLowerCase.capitalize } override def typeTagMember: TypeTag[_] = typeTag[Member] sealed trait Member extends MemberDecorated } 

Este es un ejemplo de uso de un nuevo par de rasgos de enumeración que creé (ubicado en este Gist ) para implementar todas las capacidades deseadas y delineadas en la definición de enumeración.

Una preocupación expresada es que los nombres de los miembros de la enumeración deben repetirse ( decorationOrderedSet en el ejemplo anterior). Si bien lo minimicé a una sola repetición, no pude ver cómo hacerlo aún menos debido a dos problemas:

  1. La inicialización de objeto / clase de JVM para este modelo de objeto / objeto en particular no está definida (consulte este hilo de Stackoverflow )
  2. El contenido devuelto por el método getClass.getDeclaredClasses tiene un orden indefinido (y es bastante improbable que esté en el mismo orden que las declaraciones de case object en el código fuente)

Teniendo en cuenta estos dos problemas, tuve que dejar de intentar generar un pedido implícito y tuve que exigir explícitamente al cliente que lo definiera y lo declarara con algún tipo de noción de conjunto ordenado. Como las colecciones de Scala no tienen una implementación de conjunto ordenado por inserción, lo mejor que pude hacer fue usar una List y luego comprobar en tiempo de ejecución que realmente era un conjunto. No es como hubiera preferido haber logrado esto.

Y dado el diseño requerido para esta segunda lista / valor de ordenamiento, dado el ejemplo anterior de ChessPiecesEnhancedDecorated , fue posible agregar el case object PAWN2 extends Member y luego olvida agregar Decoration(PAWN2,'P2', 2) a decorationOrderedSet . Por lo tanto, hay un control en tiempo de ejecución para verificar que la lista no es solo un conjunto, sino que contiene TODOS los objetos del caso que extienden el sealed trait Member . Esa fue una forma especial de reflexión / macro infierno para trabajar.

Por favor, deje comentarios y / o comentarios sobre el Gist .

Las ventajas de usar clases de casos sobre Enumeraciones son:

  • Cuando se utilizan clases de casos sellados, el comstackdor de Scala puede decir si la coincidencia está completamente especificada, por ejemplo, cuando todas las coincidencias posibles están expuestas en la statement correspondiente. Con enumeraciones, el comstackdor de Scala no puede decir.
  • Las clases de casos, naturalmente, admiten más campos que una Enumeración basada en el valor que admite un nombre y una ID.

Las ventajas de usar enumeraciones en lugar de clases de casos son:

  • Enumeraciones generalmente será un poco menos código para escribir.
  • Las enumeraciones son un poco más fáciles de entender para alguien nuevo en Scala ya que son frecuentes en otros idiomas.

Entonces, en general, si solo necesita una lista de constantes simples por nombre, use enumeraciones. De lo contrario, si necesita algo un poco más complejo o desea la seguridad adicional del comstackdor que le dice si tiene todas las coincidencias especificadas, use clases de casos.

ACTUALIZACIÓN: El siguiente código tiene un error, que se describe aquí . El siguiente progtwig de prueba funciona, pero si usara DayOfWeek.Mon (por ejemplo) antes de DayOfWeek, fallaría porque DayOfWeek no se ha inicializado (el uso de un objeto interno no provoca la inicialización de un objeto externo). Todavía puedes usar este código si haces algo como val enums = Seq( DayOfWeek ) en tu clase principal, forzando la inicialización de tus enums, o puedes usar las modificaciones de chaotic3quilibrium. ¡Esperando una enumeración basada en macro!


Si tu quieres

  • advertencias sobre coincidencias de patrones no exhaustivas
  • una ID Int asignada a cada valor enum, que puede controlar opcionalmente
  • una lista inmutable de los valores enum, en el orden en que fueron definidos
  • un mapa inmutable de nombre a valor enum
  • un mapa inmutable desde el valor id a enum
  • lugares para pegar métodos / datos para todos o determinados valores enum, o para la enumeración como un todo
  • valores enum ordenados (para que pueda probar, por ejemplo, si el día
  • la capacidad de extender una enumeración para crear otras

entonces lo siguiente puede ser de interés. Comentarios bienvenidos.

En esta implementación, hay clases base abstractas Enum y EnumVal, que amplía. Veremos esas clases en un minuto, pero primero, así es como definirías una enumeración:

 object DayOfWeek extends Enum { sealed abstract class Val extends EnumVal case object Mon extends Val; Mon() case object Tue extends Val; Tue() case object Wed extends Val; Wed() case object Thu extends Val; Thu() case object Fri extends Val; Fri() case object Sat extends Val; Sat() case object Sun extends Val; Sun() } 

Tenga en cuenta que debe usar cada valor enum (llamar a su método apply) para darle vida. [Ojalá los objetos internos no fueran flojos a menos que específicamente les pida que sean. Creo.]

Por supuesto, podríamos agregar métodos / datos a DayOfWeek, Val o los objetos de casos individuales si así lo deseamos.

Y así es cómo usarías tal enum:

 object DayOfWeekTest extends App { // To get a map from Int id to enum: println( DayOfWeek.valuesById ) // To get a map from String name to enum: println( DayOfWeek.valuesByName ) // To iterate through a list of the enum values in definition order, // which can be made different from ID order, and get their IDs and names: DayOfWeek.values foreach { v => println( v.id + " = " + v ) } // To sort by ID or name: println( DayOfWeek.values.sorted mkString ", " ) println( DayOfWeek.values.sortBy(_.toString) mkString ", " ) // To look up enum values by name: println( DayOfWeek("Tue") ) // Some[DayOfWeek.Val] println( DayOfWeek("Xyz") ) // None // To look up enum values by id: println( DayOfWeek(3) ) // Some[DayOfWeek.Val] println( DayOfWeek(9) ) // None import DayOfWeek._ // To compare enums as ordinals: println( Tue < Fri ) // Warnings about non-exhaustive pattern matches: def aufDeutsch( day: DayOfWeek.Val ) = day match { case Mon => "Montag" case Tue => "Dienstag" case Wed => "Mittwoch" case Thu => "Donnerstag" case Fri => "Freitag" // Commenting these out causes compiler warning: "match is not exhaustive!" // case Sat => "Samstag" // case Sun => "Sonntag" } } 

Esto es lo que obtienes al comstackrlo:

 DayOfWeekTest.scala:31: warning: match is not exhaustive! missing combination Sat missing combination Sun def aufDeutsch( day: DayOfWeek.Val ) = day match { ^ one warning found 

Puede reemplazar “coincidencia de día” con “coincidencia de día (@unchecked)” donde no desea tales advertencias, o simplemente incluir un caso de comodín al final.

Cuando ejecuta el progtwig anterior, obtiene este resultado:

 Map(0 -> Mon, 5 -> Sat, 1 -> Tue, 6 -> Sun, 2 -> Wed, 3 -> Thu, 4 -> Fri) Map(Thu -> Thu, Sat -> Sat, Tue -> Tue, Sun -> Sun, Mon -> Mon, Wed -> Wed, Fri -> Fri) 0 = Mon 1 = Tue 2 = Wed 3 = Thu 4 = Fri 5 = Sat 6 = Sun Mon, Tue, Wed, Thu, Fri, Sat, Sun Fri, Mon, Sat, Sun, Thu, Tue, Wed Some(Tue) None Some(Thu) None true 

Tenga en cuenta que dado que la Lista y los Mapas son inmutables, puede eliminar elementos fácilmente para crear subconjuntos, sin romper la enumeración en sí.

Aquí está la clase Enum en sí misma (y EnumVal dentro de ella):

 abstract class Enum { type Val <: EnumVal protected var nextId: Int = 0 private var values_ = List[Val]() private var valuesById_ = Map[Int ,Val]() private var valuesByName_ = Map[String,Val]() def values = values_ def valuesById = valuesById_ def valuesByName = valuesByName_ def apply( id : Int ) = valuesById .get(id ) // Some|None def apply( name: String ) = valuesByName.get(name) // Some|None // Base class for enum values; it registers the value with the Enum. protected abstract class EnumVal extends Ordered[Val] { val theVal = this.asInstanceOf[Val] // only extend EnumVal to Val val id = nextId def bumpId { nextId += 1 } def compare( that:Val ) = this.id - that.id def apply() { if ( valuesById_.get(id) != None ) throw new Exception( "cannot init " + this + " enum value twice" ) bumpId values_ ++= List(theVal) valuesById_ += ( id -> theVal ) valuesByName_ += ( toString -> theVal ) } } } 

Y aquí hay un uso más avanzado que controla los ID y agrega datos / métodos a la abstracción de Val y a la propia enumeración:

 object DayOfWeek extends Enum { sealed abstract class Val( val isWeekday:Boolean = true ) extends EnumVal { def isWeekend = !isWeekday val abbrev = toString take 3 } case object Monday extends Val; Monday() case object Tuesday extends Val; Tuesday() case object Wednesday extends Val; Wednesday() case object Thursday extends Val; Thursday() case object Friday extends Val; Friday() nextId = -2 case object Saturday extends Val(false); Saturday() case object Sunday extends Val(false); Sunday() val (weekDays,weekendDays) = values partition (_.isWeekday) } 

Aquí tengo una buena lib sencilla que te permite usar rasgos / clases sellados como valores enum sin tener que mantener tu propia lista de valores. Se basa en una macro simple que no depende de las knownDirectSubclasses .

https://github.com/lloydmeta/enumeratum

Actualización de marzo de 2017: según lo comentó Anthony Accioly , se ha cerrado el proceso de scala.Enumeration/enum .

Dotty (el comstackdor de la próxima generación de Scala) tomará la delantera, aunque el tema de los 70 y el PR 1958 de Martin Odersky .


Nota: ahora hay (agosto de 2016, más de 6 años después) una propuesta para eliminar scala.Enumeration . scala.Enumeration : PR 5352

Deprecate scala.Enumeration , agrega @enum annotation

La syntax

 @enum class Toggle { ON OFF } 

es un posible ejemplo de implementación, la intención es también admitir ADT que se ajusten a ciertas restricciones (sin anidamiento, recursión o parámetros de constructor variables), por ejemplo:

 @enum sealed trait Toggle case object ON extends Toggle case object OFF extends Toggle 

Desprecia el desastre total que es scala.Enumeration . scala.Enumeration .

Ventajas de @enum sobre scala.Enumeration:

  • En realidad funciona
  • Interoperabilidad de Java
  • Sin problemas de borrado
  • No hay mini DSL confuso para aprender a la hora de definir enumeraciones

Desventajas: ninguna.

Esto soluciona el problema de no poder tener una base de código compatible con Scala-JVM, Scala.js y Scala-Native (el código fuente de Java no es compatible con Scala.js/Scala-Native , el código fuente de Scala no puede definir enumeraciones que son aceptado por las API existentes en Scala-JVM).

Otra desventaja de las clases de casos frente a Enumeraciones cuando tendrá que iterar o filtrar en todas las instancias. Esta es una capacidad incorporada de Enumeration (y las enumeraciones de Java también), mientras que las clases de casos no admiten automáticamente dicha capacidad.

En otras palabras: “no hay una forma fácil de obtener una lista del conjunto total de valores enumerados con clases de casos”.

Si realmente quiere mantener la interoperabilidad con otros lenguajes de JVM (por ejemplo, Java), la mejor opción es escribir enumeraciones de Java. Esos funcionan de forma transparente tanto desde el código de Scala como desde el código de Java, que es más de lo que se puede decir para scala.Enumeration o case objects. ¡No tengamos una nueva biblioteca de enumeraciones para cada nuevo proyecto de pasatiempos en GitHub, si puede evitarse!

He visto varias versiones de hacer que una clase de caso imite una enumeración. Aquí está mi versión:

 trait CaseEnumValue { def name:String } trait CaseEnum { type V <: CaseEnumValue def values:List[V] def unapply(name:String):Option[String] = { if (values.exists(_.name == name)) Some(name) else None } def unapply(value:V):String = { return value.name } def apply(name:String):Option[V] = { values.find(_.name == name) } } 

Lo que le permite construir clases de casos que se parecen a los siguientes:

 abstract class Currency(override name:String) extends CaseEnumValue { } object Currency extends CaseEnum { type V = Site case object EUR extends Currency("EUR") case object GBP extends Currency("GBP") var values = List(EUR, GBP) } 

Tal vez alguien podría llegar a un truco mejor que simplemente agregar una clase de cada caso a la lista como lo hice. Esto fue todo lo que pude pensar en ese momento.

He estado yendo y viniendo en estas dos opciones las últimas veces que las he necesitado. Hasta hace poco, mi preferencia era la opción de objeto de rasgo / caso sellado.

1) Declaración de enumeración de Scala

 object OutboundMarketMakerEntryPointType extends Enumeration { type OutboundMarketMakerEntryPointType = Value val Alpha, Beta = Value } 

2) Rasgos sellados + objetos del caso

 sealed trait OutboundMarketMakerEntryPointType case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType case object BetaEntryPoint extends OutboundMarketMakerEntryPointType 

Si bien ninguno de estos realmente cumple con todo lo que una enumeración Java le ofrece, a continuación se detallan los pros y los contras:

Enumeración Scala

Pros: -Funciones para la creación de instancias con opción o directamente asumiendo precisión (más fácil cuando se carga desde una tienda persistente) -La interpretación sobre todos los valores posibles es compatible

Contras: no se admite la advertencia de comstackción para la búsqueda no exhaustiva (hace que la coincidencia de patrones sea menos ideal)

Objetos de caso / rasgos sellados

Pros: -Usando rasgos sellados, podemos preinstalar algunos valores mientras que otros pueden inyectarse en el momento de la creación -soporte completo para la coincidencia de patrones (aplicar / desaplicar métodos definidos)

Contras: -Instantándose desde una tienda persistente – a menudo tiene que usar la coincidencia de patrones aquí o definir su propia lista de todos los posibles ‘valores de enum’

Lo que finalmente me hizo cambiar mi opinión fue algo así como el siguiente fragmento:

 object DbInstrumentQueries { def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = { val symbol = rs.getString(tableAlias + ".name") val quoteCurrency = rs.getString(tableAlias + ".quote_currency") val fixRepresentation = rs.getString(tableAlias + ".fix_representation") val pointsValue = rs.getInt(tableAlias + ".points_value") val instrumentType = InstrumentType.fromString(rs.getString(tableAlias +".instrument_type")) val productType = ProductType.fromString(rs.getString(tableAlias + ".product_type")) Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType) } } object InstrumentType { def fromString(instrumentType: String): InstrumentType = Seq(CurrencyPair, Metal, CFD) .find(_.toString == instrumentType).get } object ProductType { def fromString(productType: String): ProductType = Seq(Commodity, Currency, Index) .find(_.toString == productType).get } 

The .get calls were hideous – using enumeration instead I can simply call the withName method on the enumeration as follows:

 object DbInstrumentQueries { def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = { val symbol = rs.getString(tableAlias + ".name") val quoteCurrency = rs.getString(tableAlias + ".quote_currency") val fixRepresentation = rs.getString(tableAlias + ".fix_representation") val pointsValue = rs.getInt(tableAlias + ".points_value") val instrumentType = InstrumentType.withNameString(rs.getString(tableAlias + ".instrument_type")) val productType = ProductType.withName(rs.getString(tableAlias + ".product_type")) Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType) } } 

So I think my preference going forward is to use Enumerations when the values are intended to be accessed from a repository and case objects/sealed traits otherwise.

I prefer case objects (it’s a matter of personal preference). To cope with the problems inherent to that approach (parse string and iterate over all elements), I’ve added a few lines that are not perfect, but are effective.

I’m pasting you the code here expecting it could be useful, and also that others could improve it.

 /** * Enum for Genre. It contains the type, objects, elements set and parse method. * * This approach supports: * * - Pattern matching * - Parse from name * - Get all elements */ object Genre { sealed trait Genre case object MALE extends Genre case object FEMALE extends Genre val elements = Set (MALE, FEMALE) // You have to take care this set matches all objects def apply (code: String) = if (MALE.toString == code) MALE else if (FEMALE.toString == code) FEMALE else throw new IllegalArgumentException } /** * Enum usage (and tests). */ object GenreTest extends App { import Genre._ val m1 = MALE val m2 = Genre ("MALE") assert (m1 == m2) assert (m1.toString == "MALE") val f1 = FEMALE val f2 = Genre ("FEMALE") assert (f1 == f2) assert (f1.toString == "FEMALE") try { Genre (null) assert (false) } catch { case e: IllegalArgumentException => assert (true) } try { Genre ("male") assert (false) } catch { case e: IllegalArgumentException => assert (true) } Genre.elements.foreach { println } } 

For those still looking how to get GatesDa’s answer to work : You can just reference the case object after declaring it to instantiate it:

 trait Enum[A] { trait Value { self: A => _values :+= this } private var _values = List.empty[A] def values = _values } sealed trait Currency extends Currency.Value object Currency extends Enum[Currency] { case object EUR extends Currency; EUR //THIS IS ONLY CHANGE case object GBP extends Currency; GBP //Inline looks better }