programar - scala introduccion
Objetos de casos vs enumeraciones en Scala (13)
¿Existe alguna guía de mejores prácticas sobre cuándo usar clases de casos (u objetos de casos) versus extender la enumeración en Scala?
Parecen ofrecer algunos de los mismos beneficios.
ACTUALIZACIÓN: El código siguiente tiene un error, que se describe aquí . El siguiente programa 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 hace que se inicialice un objeto externo). Aún 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 macros!
Si tu quieres
- Advertencias sobre coincidencias de patrones no exhaustivos.
- una identificación Int asignada a cada valor de enumeración, que puede controlar opcionalmente
- una lista inmutable de los valores de enumeración, en el orden en que se definieron
- un mapa inmutable de nombre a valor enum
- un mapa inmutable de id a valor de enumeración
- lugares para pegar métodos / datos para todos o valores de enumeración particulares, o para la enumeración como un todo
- valores de enumeración ordenados (para que pueda probar, por ejemplo, si día <miércoles)
- la capacidad de extender una enumeración para crear otros
entonces lo siguiente puede ser de interés. Comentarios bienvenidos.
En esta implementación hay clases básicas abstractas Enum y EnumVal, que se extienden. Veremos esas clases en un minuto, pero primero, así es como definiría 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 de enumeración (llame a su método de aplicación) para darle vida. [Desearía que los objetos internos no fueran perezosos a menos que pida específicamente que sean. Yo creo que.]
Por supuesto, podríamos agregar métodos / datos a DayOfWeek, Val o los objetos de casos individuales si así lo deseamos.
Y así es como usarías tal enumeración:
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 cuando lo compilas:
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 "match del día" con "(day: @unchecked) match" en el que no desea tales advertencias, o simplemente incluir un caso de captura al final.
Cuando ejecutas el programa anterior, obtienes esta salida:
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 fácilmente elementos para crear subconjuntos, sin romper la enumeración.
Aquí está la propia clase Enum (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 de la misma que controla los ID y agrega datos / métodos a la abstracción de Val y a la enumeración en sí:
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)
}
Actualización de marzo de 2017: como lo comentó Anthony Accioly , se ha cerrado el scala.Enumeration/enum
PR.
Dotty (el compilador de la próxima generación para Scala) tomará la iniciativa, aunque la edición de 1970 y la de PR de Martin Odersky en 1958 .
Nota: ahora hay (agosto de 2016, 6+ años más tarde) una propuesta para eliminar scala.Enumeration
. scala.Enumeration
: PR 5352
scala.Enumeration
, agregar@enum
anotaciónLa sintaxis
@enum
class Toggle {
ON
OFF
}
es un posible ejemplo de implementación, la intención es también admitir ADT que cumplan con ciertas restricciones (sin anidamiento, recursión o parámetros variables del constructor), por ejemplo:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Desaprueba el desastre no
scala.Enumeration
que esscala.Enumeration
.Ventajas de @enum sobre scala.Enumeration:
- En realidad funciona
- Interoperabilidad de Java
- No hay problemas de borrado.
- No hay mini-DSL confuso para aprender al definir enumeraciones
Desventajas: Ninguna.
Esto aborda el problema de no poder tener una base de código que admita Scala-JVM,
Scala.js
y Scala-Native (el código fuente de Java no es compatible conScala.js/Scala-Native
, el código fuente de Scala no puede definir enumeraciones que son aceptado por las API existentes en Scala-JVM).
He estado yendo y viniendo en estas dos opciones las últimas veces que las he necesitado. Hasta hace poco, mi preferencia ha sido para la opción de objeto de caso / rasgo sellado.
1) Declaración de enumeración Scala
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Rasgos sellados + objetos de la caja
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Si bien ninguno de estos realmente cumple con todo lo que te ofrece una enumeración de java, a continuación se muestran los pros y los contras:
Enumeración de Scala
Pros: -Funciones para crear instancias con la opción o suponer directamente que son precisas (más fáciles cuando se cargan desde un almacén persistente) -Se admite la iteración de todos los valores posibles
Contras: - La advertencia de compilación para búsquedas no exhaustivas no es compatible (hace que la coincidencia de patrones sea menos ideal)
Objetos de casos / rasgos sellados
Pros: -Utilizando rasgos sellados, podemos pre-crear instancias de algunos valores, mientras que otros pueden inyectarse en el momento de la creación, soporte para la coincidencia de patrones (métodos definidos de aplicación / no aplicación)
Contras: -Instanciación desde un almacén persistente: a menudo tiene que usar la coincidencia de patrones aquí o definir su propia lista de todos los posibles ''valores de enumeración''
Lo que finalmente me hizo cambiar mi opinión fue algo como el siguiente fragmento de código:
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
}
Las .get
llamadas eran horribles; en su lugar, utilizando la enumeración, simplemente puedo llamar al método withName en la enumeración de la siguiente manera:
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)
}
}
Así que creo que mi preferencia en el futuro es usar Enumeraciones cuando los valores están destinados a ser accedidos desde un repositorio y objetos de caso / rasgos sellados de otra manera.
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 lo siguiente:
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 encontrar un truco mejor que simplemente agregar una clase de cada caso a la lista, como hice yo. Esto fue todo lo que pude encontrar en ese momento.
Las ventajas de usar clases de casos sobre enumeraciones son:
- Cuando se usan clases de casos sellados, el compilador de Scala puede decir si la coincidencia está completamente especificada, por ejemplo, cuando todas las coincidencias posibles se incluyen en la declaración correspondiente. Con las enumeraciones, el compilador de Scala no puede decir.
- Las clases de casos, naturalmente, admiten más campos que una enumeración basada en valor que admite un nombre y una ID.
Las ventajas de usar enumeraciones en lugar de clases de casos son:
- Las enumeraciones serán generalmente un poco menos de código para escribir.
- Las enumeraciones son un poco más fáciles de entender para alguien nuevo en Scala, ya que prevalecen 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 compilador que le dice si ha especificado todas las coincidencias, utilice clases de casos.
Los objetos de caso ya devuelven su nombre para sus métodos toString, por lo que pasarlo por separado no es necesario. Aquí hay una versión similar a la de 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 perezosos; Al usar vals, en lugar de eso, podemos eliminar 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 le importa hacer trampa, puede cargar sus valores de enumeración previamente utilizando la API de reflexión o algo así como Google Reflections. Los objetos de caso no perezosos le dan la sintaxis 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 Scala idiomático:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Otra desventaja de las clases de casos frente a las enumeraciones cuando necesitará iterar o filtrar todas las instancias. Esta es una capacidad incorporada de Enumeration (y enum de Java también), mientras que las clases de casos no admiten automáticamente dicha capacidad.
En otras palabras: "no hay una manera fácil de obtener una lista del conjunto total de valores enumerados con clases de casos".
Si realmente desea mantener la interoperabilidad con otros lenguajes JVM (por ejemplo, Java), la mejor opción es escribir las enumeraciones de Java. Estos funcionan de forma transparente desde Scala y código Java, que es más de lo que se puede decir para scala.Enumeration
o los objetos de casos. ¡No tengamos una nueva biblioteca de enumeraciones para cada nuevo proyecto de hobby en GitHub, si se puede evitar!
Tengo una buena biblioteca simple aquí que te permite usar rasgos / clases sellados como valores enumeración sin tener que mantener tu propia lista de valores. Se basa en una macro simple que no depende de los buggy knownDirectSubclasses
.
ACTUALIZACIÓN: Se ha creado una nueva solución basada en macros que es muy superior a la solución que describo a continuación. Recomiendo encarecidamente utilizar esta nueva solución basada en macros . Y parece que los planes para Dotty harán que este estilo de solución de enumeración sea parte del lenguaje. Whoohoo!
Resumen:
Hay tres patrones básicos para intentar reproducir 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", hace ... pero tiene complicaciones de inicialización de clase / objeto JVM que resultan en una generación de índice ordinal inconsistente.
He creado una solución con dos clases; Enumeration and EnumerationDecorated , ubicado en esta Gist . No publiqué el código en este hilo porque el archivo para la enumeración era bastante grande (+400 líneas - contiene muchos comentarios que explican el contexto de la implementación).
Detalles:
La pregunta que estás haciendo es bastante general; "... cuándo usar objects
clases de case
vs extender [scala.]Enumeration
". Y resulta que hay MUCHAS respuestas posibles, cada respuesta dependiendo de las sutilezas de los requisitos específicos del proyecto que tiene. La respuesta se puede reducir a tres patrones básicos.
Para empezar, asegurémonos de que trabajamos con 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) :
- Contiene un conjunto cerrado ordenado naturalmente de miembros nombrados
- Hay un número fijo de miembros
- Los miembros son naturalmente ordenados e indexados explícitamente
- A diferencia de ser ordenados en base a algunos criterios no calificables de miembros inate
- Cada miembro tiene un nombre único dentro del conjunto total de todos los miembros
- Todos los miembros pueden ser iterados fácilmente a través de sus índices
- Se puede recuperar un miembro con su nombre (distingue entre mayúsculas y minúsculas)
- Sería bastante bueno si un miembro también pudiera recuperarse con su nombre que no distingue entre mayúsculas y minúsculas
- Un miembro puede ser recuperado con su índice
- Los miembros pueden usar la serialización de manera fácil, transparente y eficiente.
- Los miembros pueden ampliarse fácilmente para mantener datos adicionales asociados de singleton-ness
- Pensando más allá del
Enum
de Java, sería bueno poder aprovechar explícitamente la comprobación de exhaustividad de coincidencia de patrones de Scala para una enumeración
A continuación, veamos las versiones resumidas 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:
- 3.1 - Sería bastante bueno si un miembro también pudiera ser recuperado con su nombre que no distingue entre mayúsculas y minúsculas
- 7 - Pensando más allá del Enum de Java, sería bueno poder aprovechar explícitamente la comprobación de exhaustividad de coincidencia de patrones de Scala para una enumeración
Para mis proyectos actuales, no tengo la ventaja de asumir los riesgos en torno a la vía de proyectos mixtos de Scala / Java. E incluso si pudiera elegir hacer un proyecto mixto, el elemento 7 es fundamental para permitirme detectar problemas de tiempo de compilació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 de " 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.2 - Los miembros son naturalmente ordenados e indexados explícitamente
- 2 - Todos los miembros pueden ser iterados fácilmente según sus índices.
- 3 - Se puede recuperar un miembro con su nombre (distingue mayúsculas y minúsculas)
- 3.1 - Sería bastante bueno si un miembro también pudiera ser recuperado con su nombre que no distingue entre mayúsculas y minúsculas
- 4 - Un miembro puede ser recuperado con su índice
Es discutible que realmente cumpla con los ítems de definición de enumeración 5 y 6. Para 5, es exagerado afirmar que es eficiente. Para 6, no es realmente fácil de extender para mantener datos adicionales asociados de singleton-ness.
C) Utilizando el patrón scala.Enumeration
(inspirado en esta respuesta de ):
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 (resulta que son idénticos a la lista para usar directamente el Enum de Java):
- 3.1 - Sería bastante bueno si un miembro también pudiera ser recuperado con su nombre que no distingue entre mayúsculas y minúsculas
- 7 - Pensando más allá del Enum de Java, sería bueno poder aprovechar explícitamente la comprobación de exhaustividad de coincidencia de patrones de Scala para una enumeración
Nuevamente para mis proyectos actuales, el artículo 7 es crítico para permitirme detectar problemas de tiempo de compilación cuando añado / 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 que se describe en la definición de enumeración anterior:
- Java Enum directamente en un proyecto mixto de Scala / Java
- "Rasgo sellado + objetos de caja"
- scala.Enumeracion
Cada una de estas soluciones puede ser reelaborada / ampliada / refactorizada para tratar de cubrir algunos de los requisitos faltantes de cada uno. Sin embargo, ni Java Enum
ni las soluciones de scala.Enumeration
pueden expandirse lo suficiente 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. Prefiero las advertencias / errores de tiempo de compilación para indicar que tengo un espacio / problema en mi código en lugar de tener que extraerlo de una excepción / falla en tiempo de ejecución de producción.
En ese sentido, me puse a trabajar con la ruta del case object
para ver si podía producir una solución que cubriera toda la definición de enumeración anterior. El primer desafío fue empujar a través del núcleo del problema de inicialización de clase / objeto JVM (cubierto en detalle en esta publicación de ). Y finalmente pude encontrar una solución.
Como mi solución son dos rasgos; Enumeration and EnumerationDecorated , y dado que el rasgo Enumeration
tiene más de 400 líneas (hay muchos comentarios que explican el contexto), me estoy absteniendo de pegarlo en este hilo (lo que haría que se alargue considerablemente la página). Para más detalles, salta directamente a la Gist .
Esto es lo que la solución termina pareciendo usar la misma idea de datos que la anterior (versión completamente comentada disponible aquí ) e implementada 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 Gist ) para implementar todas las capacidades deseadas y descritas 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é en una sola repetición, no pude ver cómo hacerlo aún menos debido a dos problemas:
- La inicialización de objeto / clase JVM para este objeto en particular / modelo de objeto de caso no está definida (vea este hilo de )
- El contenido devuelto por el método
getClass.getDeclaredClasses
tiene un orden indefinido (y es poco probable que esté en el mismo orden que las declaraciones decase object
en el código fuente)
Dados estos dos problemas, tuve que renunciar a intentar generar un pedido implícito y tuve que requerir explícitamente que el cliente lo definiera y lo declarara con algún tipo de concepto de conjunto ordenado. Como las colecciones de Scala no tienen una implementación de conjunto ordenado de inserción, lo mejor que pude hacer fue usar una List
y luego verificar en tiempo de ejecución que era realmente un conjunto. No es como hubiera preferido haber logrado esto.
Y dado el diseño requerido para este segundo valor de pedido de lista / conjunto, dado el ejemplo anterior de ChessPiecesEnhancedDecorated
, fue posible agregar el case object PAWN2 extends Member
y luego olvidar agregar Decoration(PAWN2,''P2'', 2)
a decorationOrderedSet
. Por lo tanto, hay una verificación de tiempo de ejecución para verificar que la lista no es solo un conjunto, sino que contiene TODOS los objetos de 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 la Gist .
Una gran diferencia es que los Enumeration
vienen con soporte para crear instancias desde algún name
String. 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 se desea conservar las enumeraciones (por ejemplo, en una base de datos) o crearlas a partir de datos que residen en archivos. Sin embargo, en general, encuentro que las enumeraciones son un poco torpes en Scala y tengo la sensación de ser 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 "UnknownCurrency (code)", hay otras formas de controlar el hecho de no encontrar una cadena de código de moneda que "romper" la naturaleza de conjunto cerrado del tipo de
Currency
.UnknownCurrency
Currency
es de tipoCurrency
ahora puede colarse en otras partes de una API.Es recomendable empujar ese caso fuera de la
Enumeration
y hacer que el cliente trate con un tipo deOption[Currency]
que indique claramente que realmente hay un problema coincidente y "aliente" al usuario de la API a resolverlo él mismo.
Para hacer un seguimiento de las otras respuestas aquí, los principales inconvenientes de los case object
de case object
sobre las Enumeration
son:
No se puede iterar sobre todas las instancias de la "enumeración" . Este es ciertamente el caso, pero en la práctica me parece extremadamente raro que se requiera.
No se puede crear una instancia fácilmente del valor persistido . Esto también es cierto, pero, excepto en el caso de grandes enumeraciones (por ejemplo, todas las monedas), no presenta una sobrecarga enorme.
Para aquellos que aún buscan cómo funciona la respuesta de GatesDa : puede hacer referencia al objeto del caso después de declararlo para instanciarlo:
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
}
Yo prefiero case objects
(es una cuestión de preferencia personal). Para hacer frente a los problemas inherentes a ese enfoque (analizar la cadena e iterar sobre todos los elementos), he agregado algunas líneas que no son perfectas, pero son efectivas.
Te estoy pegando el código aquí esperando que pueda ser útil, y también que otros puedan mejorarlo.
/**
* 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 }
}