¿Hay alguna guía de mejores prácticas sobre cuándo usar las clases de casos? (u objetos de casos) en lugar de extender la enumeración en Scala?
Parecen ofrecer algunos de los mismos beneficios.
scala
enumeration
case-class
Alex Miller
fuente
fuente
enum
(para mediados de 2020).Respuestas:
Una gran diferencia es que
Enumeration
viene con soporte para crear instancias de algunosname
String. Por ejemplo:Entonces puedes hacer:
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, encuentro en general que las enumeraciones son un poco torpes en Scala y tienen la sensación de un complemento incómodo, por lo que ahora tiendo a usar
case object
s. Acase object
es más flexible que una enumeración:Entonces ahora tengo la ventaja de ...
Como señaló @ chaotic3quilibrium (con algunas correcciones para facilitar la lectura):
Para seguir las otras respuestas aquí, los principales inconvenientes de
case object
s sobreEnumeration
s son:No se puede iterar sobre todas las instancias de la "enumeración" . Este es ciertamente el caso, pero me ha resultado extremadamente raro en la práctica que sea necesario.
No se puede crear una instancia fácilmente del valor persistente . Esto también es cierto, pero, excepto en el caso de grandes enumeraciones (por ejemplo, todas las monedas), esto no presenta una gran sobrecarga.
fuente
trade.ccy
con el ejemplo de rasgo sellado.case
object
genera una huella de código mayor (~ 4x) queEnumeration
? Distinción útil especialmente parascala.js
proyectos que necesitan una pequeña huella.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 harán que este estilo de solución de enumeración sea parte del lenguaje. Whoohoo!
Resumen:
Existen tres patrones básicos para intentar reproducir Java
Enum
dentro de un proyecto Scala. Dos de los tres patrones; directamente utilizando JavaEnum
yscala.Enumeration
, no son capaces de habilitar la exhaustiva coincidencia de patrones de Scala. Y el tercero; "rasgo sellado + objeto de caso", sí ... 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; Enumeración y Enumeración Decorado , ubicado en este Gist . No publiqué el código en este hilo ya que el archivo para Enumeration era bastante grande (+400 líneas - contiene muchos comentarios que explican el contexto de implementación).
Detalles:
la pregunta que hace es bastante general; "... cuándo usar
case
clasesobjects
vs 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 se puede reducir a tres patrones básicos.Para comenzar, asegurémonos de que trabajamos desde la misma idea básica de qué es una enumeración. Definamos una enumeración principalmente en términos de lo
Enum
proporcionado a partir de Java 5 (1.5) :Enum
, sería bueno poder explícitamente aprovechar la comprobación de exhaustividad de coincidencia de patrones de Scala para una enumeraciónA continuación, echemos un vistazo a las versiones resumidas de los tres patrones de solución más comunes publicados:
A) Realmente usando directamente el patrón Java
Enum
(en un proyecto mixto Scala / Java):Los siguientes elementos de la definición de enumeración no están disponibles:
Para mis proyectos actuales, no tengo el beneficio de correr riesgos en torno a la ruta del proyecto mixto Scala / Java. E incluso si pudiera elegir hacer un proyecto mixto, el elemento 7 es crítico 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 los miembros de enumeración existentes.
B) Usando el patrón "
sealed trait
+case objects
":Los siguientes elementos de la definición de enumeración no están disponibles:
Es discutible que realmente cumpla con los elementos 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 extenderlo para contener datos adicionales asociados de soltería.
C) Usando el
scala.Enumeration
patrón (inspirado en esta respuesta de StackOverflow ):Los siguientes elementos de la definición de enumeración no están disponibles (resulta ser idéntico a la lista para usar directamente la enumeración de Java):
Nuevamente para mis proyectos actuales, el ítem 7 es crítico 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 los miembros de enumeración existentes.
Entonces, 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:
Cada una de estas soluciones puede eventualmente ser modificada / ampliada / refactorizada para intentar cubrir algunos de los requisitos faltantes de cada uno. Sin embargo, ni Java
Enum
ni lasscala.Enumeration
soluciones pueden expandirse lo suficiente como para proporcionar el ítem 7. Y para mis propios proyectos, este es uno de los valores más convincentes de usar un tipo cerrado dentro de Scala. Prefiero encarecidamente las advertencias / errores de tiempo de compilación para indicar que tengo un espacio / problema en mi código en lugar de tener que recogerlo de una excepción / falla de tiempo de ejecución de producción.En ese sentido, me puse a trabajar con la
case object
vía para ver si podía producir una solución que cubriera toda la definición de enumeración anterior. El primer desafío fue superar el núcleo del problema de inicialización de clase / objeto 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
Enumeration
rasgo tiene más de +400 líneas de largo (muchos comentarios explican el contexto), renuncio a pegarlo en este hilo (lo que haría que se extienda considerablemente por la página). Para más detalles, vaya directamente a Gist .Así es como se ve la solución usando la misma idea de datos que la anterior (versión totalmente comentada disponible aquí ) e implementada en
EnumerationDecorated
.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 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é a una sola repetición, no pude ver cómo hacerlo aún menos debido a dos problemas:getClass.getDeclaredClasses
tiene un orden indefinido (y es poco probable que esté en el mismo orden que lascase object
declaraciones en el código fuente)Dados estos dos problemas, tuve que renunciar a intentar generar un pedido implícito y tuve que exigir explícitamente que el cliente lo defina y lo declare 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 un
List
y luego verificar que realmente era un conjunto. No es como hubiera preferido haber logrado esto.Y teniendo en cuenta el diseño requiere esta segunda lista / pedido conjunto
val
, dado elChessPiecesEnhancedDecorated
ejemplo anterior, era posible añadircase object PAWN2 extends Member
y luego olvidarse de añadirDecoration(PAWN2,'P2', 2)
adecorationOrderedSet
. Por lo tanto, hay una comprobació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 elsealed trait Member
. Esa fue una forma especial de reflexión / macro infierno para trabajar.Por favor, deje comentarios y / o comentarios sobre el Gist .
fuente
org.scalaolio.util.Enumeration
yorg.scalaolio.util.EnumerationDecorated
: scalaolio.orgLos objetos de caso ya devuelven su nombre para sus métodos toString, por lo que no es necesario pasarlo por separado. Aquí hay una versión similar a la de jho (métodos de conveniencia omitidos por brevedad):
Los objetos son vagos; al usar vals en su lugar, podemos soltar la lista pero tenemos que repetir el nombre:
Si no le importa hacer trampa, puede precargar sus valores de enumeración usando la API de reflexión o algo así como Google Reflections. Los objetos de caso no perezosos le dan la sintaxis más limpia:
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 coincidan mejor con el código idiomático de Scala:
fuente
Currency.values
, solo obtengo valores a los que he accedido anteriormente. ¿Hay alguna forma de evitar eso?Las ventajas de usar clases de caso sobre Enumeraciones son:
Las ventajas de usar Enumeraciones en lugar de clases de casos son:
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 que la seguridad adicional del compilador le diga si tiene todas las coincidencias especificadas, use las clases de casos.
fuente
ACTUALIZACIÓN: El siguiente código tiene un error, que se describe aquí . El siguiente programa de prueba funciona, pero si fuera a usar 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). Todavía puede usar este código si hace algo como
val enums = Seq( DayOfWeek )
en su clase principal, forzando la inicialización de sus enumeraciones, o puede usar las modificaciones de chaotic3quilibrium. ¡Esperamos una enumeración basada en macros!Si tu quieres
entonces lo siguiente puede ser de interés. Comentarios bienvenidos.
En esta implementación hay clases base abstractas Enum y EnumVal, que usted extiende. Veremos esas clases en un minuto, pero primero, así es como definiría una enumeración:
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 lo sean. Yo creo que.]
Por supuesto, podríamos agregar métodos / datos a DayOfWeek, Val o los objetos de caso individuales si así lo deseáramos.
Y así es como usarías tal enumeración:
Esto es lo que obtienes cuando lo compilas:
Puede reemplazar "coincidencia de día" con "coincidencia (día: @ no comprobado)" donde no desea tales advertencias, o simplemente incluir un caso general al final.
Cuando ejecuta el programa anterior, obtiene esta salida:
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 clase Enum en sí (y EnumVal dentro de ella):
Y aquí hay un uso más avanzado que controla los ID y agrega datos / métodos a la abstracción Val y a la enumeración misma:
fuente
var
] es un pecado mortal límite en el mundo de la PF". No creo que la opinión sea universalmente aceptada.Tengo una buena biblioteca simple aquí que le permite usar rasgos / clases selladas como valores de enumeración sin tener que mantener su propia lista de valores. Se basa en una macro simple que no depende del buggy
knownDirectSubclasses
.https://github.com/lloydmeta/enumeratum
fuente
Actualización de marzo de 2017: como comentó Anthony Accioly , el
scala.Enumeration/enum
RP ha sido cerrado.Dotty (compilador de la próxima generación para Scala) tomará la delantera, aunque en el número de 1970 y en 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
: PR 5352fuente
Otra desventaja de las clases de casos versus Enumeraciones cuando necesitará 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 manera fácil de obtener una lista del conjunto total de valores enumerados con clases de casos".
fuente
Si realmente quiere mantener la interoperabilidad con otros lenguajes JVM (por ejemplo, Java), la mejor opción es escribir enumeraciones Java. Esos funcionan de manera transparente tanto desde el código Scala como desde Java, que es más de lo que se puede decir de los
scala.Enumeration
objetos de caso. ¡No podemos tener una nueva biblioteca de enumeraciones para cada nuevo proyecto de pasatiempo en GitHub, si se puede evitar!fuente
He visto varias versiones de hacer que una clase de caso imite una enumeración. Aquí está mi versión:
Lo que le permite construir clases de casos similares a las siguientes:
Tal vez alguien podría idear un truco mejor que simplemente agregar una clase de cada caso a la lista como lo hice yo. Esto fue todo lo que se me ocurrió en ese momento.
fuente
He estado yendo y viniendo de estas dos opciones las últimas veces que las he necesitado. Hasta hace poco, mi preferencia ha sido la opción de objeto de rasgo / caso sellado.
1) Declaración de enumeración Scala
2) Rasgos Sellados + Objetos de Caso
Si bien ninguno de estos realmente cumple con todo lo que una enumeración de Java le brinda, a continuación se detallan los pros y los contras:
Enumeración Scala
Pros: -Funciones para crear instancias con opción o asumir directamente la precisión (más fácil cuando se carga desde una tienda persistente) -Se admite la iteración sobre todos los valores posibles
Contras: -La advertencia de compilación para la búsqueda no exhaustiva no es compatible (hace que la coincidencia de patrones sea menos ideal)
Objetos de caso / rasgos sellados
Pros: -Utilizando rasgos sellados, podemos pre-instanciar algunos valores, mientras que otros pueden inyectarse en el momento de la creación -completo soporte para la coincidencia de patrones (métodos de aplicar / no aplicar definidos)
Contras: -Instantando desde una tienda persistente - a menudo tienes que usar la coincidencia de patrones aquí o definir tu propia lista de todos los 'valores de enumeración' posibles
Lo que finalmente me hizo cambiar mi opinión fue algo como el siguiente fragmento:
Las
.get
llamadas fueron horribles: en su lugar, usando la enumeración simplemente puedo llamar al método withName en la enumeración de la siguiente manera:Por lo tanto, creo que mi preferencia en el futuro es usar Enumeraciones cuando se pretende acceder a los valores desde un repositorio y objetos de caso / rasgos sellados de lo contrario.
fuente
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.
fuente
Para aquellos que todavía buscan cómo hacer que funcione la respuesta de GatesDa : puede hacer referencia al objeto de caso después de declararlo para instanciarlo:
fuente
Creo que la mayor ventaja de tener
case classes
másenumerations
es que puedes usar el patrón de clase de tipo también conocido como polimorfismo ad-hoc . No es necesario que coincida con enumeraciones como:en cambio tendrás algo como:
fuente