Existe-t-il des directives de bonnes pratiques sur le moment d'utiliser des classes de cas (ou des objets de cas) par rapport à l'extension de l'énumération dans Scala?
Ils semblent offrir certains des mêmes avantages.
scala
enumeration
case-class
Alex Miller
la source
la source
enum
(pour mi 2020).Réponses:
Une grande différence est que les
Enumeration
s viennent avec un support pour les instancier à partir d'unename
chaîne. Par exemple:Ensuite, vous pouvez faire:
Cela est utile lorsque vous souhaitez conserver des énumérations (par exemple, dans une base de données) ou les créer à partir de données résidant dans des fichiers. Cependant, je trouve en général que les énumérations sont un peu maladroites dans Scala et ont la sensation d'un add-on maladroit, donc j'ai maintenant tendance à utiliser l'
case object
art. Acase object
est plus flexible qu'une énumération:Alors maintenant, j'ai l'avantage de ...
Comme l'a souligné @ chaotic3quilibrium (avec quelques corrections pour faciliter la lecture):
Pour poursuivre sur les autres réponses ici, les principaux inconvénients de
case object
s surEnumeration
s sont:Impossible d'itérer sur toutes les instances de "l'énumération" . C'est certainement le cas, mais j'ai trouvé extrêmement rare dans la pratique que cela soit nécessaire.
Impossible d'instancier facilement à partir d'une valeur persistante . Cela est également vrai, mais, sauf dans le cas d'énumérations énormes (par exemple, toutes les devises), cela ne présente pas d'énormes frais généraux.
la source
trade.ccy
dans l'exemple de trait scellé.case
object
génère pas une empreinte de code plus grande (~ 4x) queEnumeration
? Distinction utile en particulier pour lesscala.js
projets nécessitant une faible empreinte.MISE À JOUR: Une nouvelle solution basée sur des macros a été créée, bien supérieure à la solution que je décris ci-dessous. Je recommande fortement d'utiliser cette nouvelle solution basée sur des macros . Et il semble que Dotty fera de ce style de solution d'énumération une partie du langage. Whoo Hoo!
Résumé:
Il existe trois modèles de base pour tenter de reproduire le Java
Enum
dans un projet Scala. Deux des trois modèles; en utilisant directement JavaEnum
etscala.Enumeration
, ne sont pas capables d'activer la correspondance de motifs exhaustive de Scala. Et le troisième; "trait scellé + objet cas", fait ... mais a des complications d'initialisation de classe / objet JVM entraînant une génération d'index ordinal incohérente.J'ai créé une solution avec deux classes; Enumeration and EnumerationDecorated , situé dans ce Gist . Je n'ai pas posté le code dans ce fil car le fichier pour l'énumération était assez volumineux (+400 lignes - contient beaucoup de commentaires expliquant le contexte d'implémentation).
Détails:
La question que vous posez est assez générale; "... quand utiliser les
case
classesobjects
vs étendre[scala.]Enumeration
". Et il s'avère qu'il y a BEAUCOUP de réponses possibles, chaque réponse dépendant des subtilités des exigences spécifiques du projet que vous avez. La réponse peut être réduite à trois modèles de base.Pour commencer, assurons-nous que nous travaillons à partir de la même idée de base de ce qu'est une énumération. Définissons une énumération principalement en termes de
Enum
fourni à partir de Java 5 (1.5) :Enum
, il serait bien de pouvoir utiliser explicitement le modèle de Scala correspondant à l'exhaustivité en vérifiant une énumérationEnsuite, regardons les versions résumées des trois modèles de solution les plus courants publiés:
A) En fait, en utilisant directement le modèle Java
Enum
(dans un projet mixte Scala / Java):Les éléments suivants de la définition d'énumération ne sont pas disponibles:
Pour mes projets en cours, je n'ai pas l'avantage de prendre des risques autour du parcours mixte Scala / Java. Et même si je pouvais choisir de faire un projet mixte, l'élément 7 est essentiel pour me permettre d'attraper les problèmes de temps de compilation si / quand j'ajoute / supprime des membres d'énumération, ou j'écris du nouveau code pour traiter les membres d'énumération existants.
B) En utilisant le modèle "
sealed trait
+case objects
":Les éléments suivants de la définition d'énumération ne sont pas disponibles:
On peut soutenir qu'il répond vraiment aux éléments de définition de l'énumération 5 et 6. Pour 5, c'est un tronçon de prétendre qu'il est efficace. Pour 6, il n'est pas vraiment facile d'étendre pour contenir des données de singleton associées supplémentaires.
C) En utilisant le
scala.Enumeration
modèle (inspiré de cette réponse StackOverflow ):Les éléments suivants de la définition d'énumération ne sont pas disponibles (il se trouve qu'ils sont identiques à la liste pour l'utilisation directe de l'énumération Java):
Encore une fois pour mes projets en cours, l'élément 7 est essentiel pour me permettre d'attraper des problèmes de temps de compilation si / quand j'ajoute / supprime des membres d'énumération, ou si j'écris du nouveau code pour traiter les membres d'énumération existants.
Donc, étant donné la définition ci-dessus d'une énumération, aucune des trois solutions ci-dessus ne fonctionne car elles ne fournissent pas tout ce qui est décrit dans la définition d'énumération ci-dessus:
Chacune de ces solutions peut éventuellement être retravaillée / étendue / refactorisée pour tenter de couvrir certaines des exigences manquantes de chacun. Cependant, ni Java
Enum
ni lesscala.Enumeration
solutions ne peuvent être suffisamment développées pour fournir l'élément 7. Et pour mes propres projets, c'est l'une des valeurs les plus convaincantes de l'utilisation d'un type fermé dans Scala. Je préfère fortement les erreurs / avertissements de temps de compilation pour indiquer que j'ai un écart / problème dans mon code plutôt que d'avoir à le glaner d'une exception / échec d'exécution de production.À cet égard, je me suis mis à travailler avec la
case object
voie pour voir si je pouvais produire une solution qui couvrait toute la définition d'énumération ci-dessus. Le premier défi a été de passer à travers le cœur du problème d'initialisation de classe / objet JVM (traité en détail dans cet article StackOverflow ). Et j'ai finalement pu trouver une solution.Comme ma solution est deux traits; Enumeration et EnumerationDecorated , et puisque le
Enumeration
trait fait plus de +400 lignes (beaucoup de commentaires expliquant le contexte), je renonce à le coller dans ce fil (ce qui le ferait considérablement descendre la page). Pour plus de détails, veuillez passer directement à l' essentiel .Voici à quoi ressemble la solution en utilisant la même idée de données que ci-dessus (version entièrement commentée disponible ici ) et implémentée dans
EnumerationDecorated
.Ceci est un exemple d'utilisation d'une nouvelle paire de traits d'énumération que j'ai créée (située dans cet Gist ) pour implémenter toutes les capacités souhaitées et décrites dans la définition d'énumération.
Une préoccupation exprimée est que les noms des membres de l'énumération doivent être répétés (
decorationOrderedSet
dans l'exemple ci-dessus). Bien que je l'ai réduit à une seule répétition, je ne voyais pas comment le rendre encore moins en raison de deux problèmes:getClass.getDeclaredClasses
a un ordre non défini (et il est peu probable qu'il soit dans le même ordre que lescase object
déclarations dans le code source)Compte tenu de ces deux problèmes, j'ai dû renoncer à générer un ordre implicite et j'ai dû explicitement demander au client de le définir et de le déclarer avec une sorte de notion d'ensemble ordonné. Comme les collections Scala n'ont pas d'implémentation d'ensemble d'insertions ordonnées, le mieux que je pouvais faire était d'utiliser un
List
et puis de vérifier à l'exécution qu'il s'agissait vraiment d'un ensemble. Ce n'est pas comme ça que j'aurais préféré y arriver.Et étant donné la conception nécessaire cette deuxième liste / commande ensemble
val
, compte tenu de l'ChessPiecesEnhancedDecorated
exemple ci - dessus, il est possible d'ajoutercase object PAWN2 extends Member
, puis oublier d'ajouterDecoration(PAWN2,'P2', 2)
àdecorationOrderedSet
. Ainsi, il y a un contrôle d'exécution pour vérifier que la liste n'est pas seulement un ensemble, mais contient TOUS les objets de cas qui étendent lesealed trait Member
. C'était une forme spéciale de réflexion / macro enfer à travailler.Veuillez laisser des commentaires et / ou des commentaires sur le Gist .
la source
org.scalaolio.util.Enumeration
etorg.scalaolio.util.EnumerationDecorated
: scalaolio.orgLes objets Case renvoient déjà leur nom pour leurs méthodes toString, il n'est donc pas nécessaire de les transmettre séparément. Voici une version similaire à celle de jho (méthodes de commodité omises par souci de concision):
Les objets sont paresseux; en utilisant vals à la place, nous pouvons supprimer la liste, mais nous devons répéter le nom:
Si cela ne vous dérange pas, vous pouvez précharger vos valeurs d'énumération en utilisant l'API de réflexion ou quelque chose comme Google Reflections. Les objets cas non paresseux vous offrent la syntaxe la plus nette:
Agréable et propre, avec tous les avantages des classes de cas et des énumérations Java. Personnellement, je définis les valeurs d'énumération en dehors de l'objet pour mieux correspondre au code Scala idiomatique:
la source
Currency.values
, je ne récupère que les valeurs auxquelles j'ai déjà accédé. Y a-t-il un moyen de contourner cela?Les avantages de l'utilisation des classes de cas par rapport aux énumérations sont les suivants:
Les avantages de l'utilisation des énumérations au lieu des classes de cas sont les suivants:
Donc, en général, si vous avez juste besoin d'une liste de constantes simples par nom, utilisez des énumérations. Sinon, si vous avez besoin de quelque chose d'un peu plus complexe ou si vous voulez la sécurité supplémentaire du compilateur vous indiquant si toutes les correspondances sont spécifiées, utilisez des classes de cas.
la source
MISE À JOUR: Le code ci-dessous a un bug, décrit ici . Le programme de test ci-dessous fonctionne, mais si vous deviez utiliser DayOfWeek.Mon (par exemple) avant DayOfWeek lui-même, il échouerait car DayOfWeek n'a pas été initialisé (l'utilisation d'un objet interne ne provoque pas l'initialisation d'un objet externe). Vous pouvez toujours utiliser ce code si vous faites quelque chose comme
val enums = Seq( DayOfWeek )
dans votre classe principale, forçant l'initialisation de vos énumérations, ou vous pouvez utiliser les modifications de chaotic3quilibrium. Dans l'attente d'une énumération basée sur les macros!Si tu veux
alors ce qui suit peut être intéressant. Commentaires bienvenus.
Dans cette implémentation, il existe des classes de base Enum et EnumVal abstraites que vous étendez. Nous verrons ces classes dans une minute, mais d'abord, voici comment vous définiriez une énumération:
Notez que vous devez utiliser chaque valeur d'énumération (appelez sa méthode apply) pour lui donner vie. [Je souhaite que les objets intérieurs ne soient pas paresseux à moins que je ne le demande spécifiquement. Je pense.]
Nous pouvons bien sûr ajouter des méthodes / données à DayOfWeek, Val ou aux objets de cas individuels si nous le souhaitons.
Et voici comment vous utiliseriez une telle énumération:
Voici ce que vous obtenez lorsque vous le compilez:
Vous pouvez remplacer "match du jour" par "(jour: @unchecked) match" où vous ne voulez pas de tels avertissements, ou simplement inclure un cas fourre-tout à la fin.
Lorsque vous exécutez le programme ci-dessus, vous obtenez cette sortie:
Notez que puisque la liste et les cartes sont immuables, vous pouvez facilement supprimer des éléments pour créer des sous-ensembles, sans casser l'énumération elle-même.
Voici la classe Enum elle-même (et EnumVal en son sein):
Et voici une utilisation plus avancée de celui-ci qui contrôle les ID et ajoute des données / méthodes à l'abstraction Val et à l'énumération elle-même:
la source
var
] est un péché mortel limite dans le monde de la PF" - je ne pense pas que l'opinion soit universellement acceptée.J'ai une belle bibliothèque simple ici qui vous permet d'utiliser des traits / classes scellés comme valeurs d'énumération sans avoir à maintenir votre propre liste de valeurs. Il s'appuie sur une simple macro qui ne dépend pas du buggy
knownDirectSubclasses
.https://github.com/lloydmeta/enumeratum
la source
Mise à jour mars 2017: comme l'a commenté Anthony Accioly , le
scala.Enumeration/enum
PR a été fermé.Dotty (compilateur de nouvelle génération pour Scala) prendra la tête, bien que le numéro de dotty 1970 et le PR 1958 de Martin Odersky .
Remarque: il y a maintenant (août 2016, 6 ans et plus plus tard) une proposition de suppression
scala.Enumeration
: PR 5352la source
Un autre inconvénient des classes de cas par rapport aux énumérations lorsque vous devrez itérer ou filtrer sur toutes les instances. Il s'agit d'une capacité intégrée d'énumération (et d'énumérations Java également) tandis que les classes de cas ne prennent pas automatiquement en charge une telle capacité.
En d'autres termes: "il n'y a pas de moyen facile d'obtenir une liste de l'ensemble total des valeurs énumérées avec les classes de cas".
la source
Si vous souhaitez sérieusement maintenir l'interopérabilité avec d'autres langages JVM (par exemple Java), la meilleure option est d'écrire des énumérations Java. Ceux-ci fonctionnent de manière transparente à partir du code Scala et Java, ce qui est plus que ce qui peut être dit pour les
scala.Enumeration
objets ou les cas. N'ayons pas une nouvelle bibliothèque d'énumérations pour chaque nouveau projet de passe-temps sur GitHub, si cela peut être évité!la source
J'ai vu différentes versions de faire une classe de cas imiter une énumération. Voici ma version:
Ce qui vous permet de construire des classes de cas qui ressemblent à ceci:
Peut-être que quelqu'un pourrait trouver une meilleure astuce que d'ajouter simplement une classe de cas à la liste comme je l'ai fait. C'est tout ce que j'ai pu trouver à l'époque.
la source
J'ai fait des allers-retours sur ces deux options les dernières fois où j'en ai eu besoin. Jusqu'à récemment, ma préférence a été pour l'option d'objet trait / cas scellé.
1) Déclaration de dénombrement Scala
2) Traits scellés + objets de caisse
Bien qu'aucun de ces éléments ne réponde vraiment à tout ce qu'une énumération java vous offre, voici les avantages et les inconvénients:
Énumération Scala
Avantages: -Fonctions pour instancier avec option ou supposant directement précis (plus facile lors du chargement à partir d'un magasin persistant) -Itération sur toutes les valeurs possibles est prise en charge
Inconvénients: l'avertissement de compilation pour la recherche non exhaustive n'est pas pris en charge (rend la correspondance des modèles moins idéale)
Objets / traits scellés
Avantages: -En utilisant des traits scellés, nous pouvons pré-instancier certaines valeurs tandis que d'autres peuvent être injectées au moment de la création
Inconvénients: -Instanciation à partir d'un magasin persistant - vous devez souvent utiliser la correspondance de modèles ici ou définir votre propre liste de toutes les "valeurs d'énumération" possibles
Ce qui m'a finalement fait changer d'avis était quelque chose comme l'extrait suivant:
Les
.get
appels étaient hideux - en utilisant l'énumération à la place, je peux simplement appeler la méthode withName sur l'énumération comme suit:Je pense donc que ma préférence pour l'avenir est d'utiliser les énumérations lorsque les valeurs sont destinées à être accessibles à partir d'un référentiel et les objets / traits scellés dans le cas contraire.
la source
je préfère
case objects
(c'est une question de préférence personnelle). Pour faire face aux problèmes inhérents à cette approche (analyser la chaîne et itérer sur tous les éléments), j'ai ajouté quelques lignes qui ne sont pas parfaites, mais qui sont efficaces.Je vous colle le code ici en espérant qu'il pourrait être utile, et que d'autres pourraient l'améliorer.
la source
Pour ceux qui cherchent encore à faire fonctionner la réponse de GatesDa : Vous pouvez simplement référencer l'objet case après l'avoir déclaré pour l'instancier:
la source
Je pense que le plus grand avantage d'avoir
case classes
plusenumerations
est que vous pouvez utiliser le modèle de classe de type aka polymorphysme ad hoc . Pas besoin de faire correspondre des énumérations comme:à la place, vous aurez quelque chose comme:
la source