Je souhaite qu'un type de données représente un ensemble fini d'entiers pouvant être traités par des noms spécifiques. Je pense que la meilleure façon de faire est d’utiliser un Enum.
Cependant, il y a un petit problème. Le seul moyen que je connaisse pour définir un Enum est quelque chose comme ceci:
data MyDataType = Foo | Bar | Baz
instance Enum MyDataType
toEnum 0 = Foo
toEnum 1 = Bar
toEnum 2 = Baz
fromEnum Foo = 0
fromEnum Bar = 1
fromEnum Baz = 2
Notez que je dois répéter la même paire deux fois - une fois lorsque vous définissez un mappage d’entier à enum et une fois lorsque vous définissez un mappage d’énum à entier.
Y a-t-il un moyen d'éviter cette répétition?
instance Enum MyDataType where
fromEnum = fromJust . flip lookup table
toEnum = fromJust . flip lookup (map swap table)
table = [(Foo, 0), (Bar, 1), (Baz, 2)]
data MyDataType = Foo | Bar | Baz deriving (Enum)
Le problème avec la solution acceptée est que le compilateur ne vous dira pas s'il vous manque une énumération dans votre table. La solution deriving Enum
est excellente, mais cela ne fonctionnera pas si vous souhaitez un mappage arbitraire sur des nombres. Une autre réponse suggère Generics ou Template Haskell. Ceci fait suite à cela en utilisant Data
.
{-# Language DeriveDataTypeable #-}
import Data.Data
data MyDataType = Foo | Bar | Baz deriving (Eq, Show, Data, Typeable)
toNumber enum = case enum of
Foo -> 1
Bar -> 2
Baz -> 4
Nous aurons un avertissement du compilateur dans le mappage de cas toNumber
quand un nouveau constructeur sera ajouté.
Maintenant, nous avons juste besoin de pouvoir transformer ce code en données afin que le mappage puisse être automatiquement inversé. Ici, nous générons la même variable table
mentionnée dans la solution acceptée.
table = map (\cData -> let c = (fromConstr cData :: MyDataType) in (c, toNumber c) )
$ dataTypeConstrs $ dataTypeOf Foo
Vous pouvez remplir une classe Enum
de la même manière que dans la réponse acceptée. Sans le mentionner, vous pouvez également renseigner la classe Bounded
.
Puisque vous dites que les chiffres ne sont pas générés par une loi en vigueur, vous pouvez utiliser une programmation générique (par exemple avec Scrap Your Boilerplate) ou un modèle Haskell pour implémenter une solution générique à ce problème. J'ai tendance à préférer Template Haskell car il génère du code et le compile, ce qui vous permet de bénéficier de tous les avantages de la vérification de type et de l'optimisation de GHC.
Je ne serais pas surpris si quelqu'un avait déjà implémenté cela. Cela devrait être trivial.
Mes exemples ici utilisent GHCI 8.4.4 avec une invite, "λ: "
.
Je pense que dériver de Enum
a plus de sens ici, car les types les plus fondamentaux en Haskell dérivent également de Enum
(n-uplets, caractères, nombres entiers, etc.), et il a des méthodes intégrées pour obtenir des valeurs dans et à partir de l'énum.
Commencez par créer un type de données dérivé de Enum
(et Show
afin de pouvoir afficher la valeur dans le REPL et Eq
pour activer la complétion de la plage ..
):
λ: data MyDataType = Foo | Bar | Baz deriving (Enum, Show, Eq)
λ: [Foo ..]
[Foo,Bar,Baz]
Les énumérations définissent une méthode, fromEnum
, que vous pouvez utiliser pour obtenir les valeurs demandées dans la question (0
, 1
et 2
).
Usage:
λ: map fromEnum [Foo ..]
[0,1,2]
Il est simple de définir une fonction donnant une valeur arbitraire (telle que des puissances de deux utilisant l'opérateur de puissance entier, ^
):
λ: value e = 2 ^ (fromEnum e)
Usage:
λ: map value [Foo ..]
[1,2,4]
Une autre réponse dit:
La solution
deriving Enum
est excellente, mais cela ne fonctionnera pas si vous souhaitez un mappage arbitraire sur des nombres.
Eh bien, voyons à ce sujet (utilisez :set +m
pour activer la saisie multiligne dans GHCI, si ce n'est déjà fait):
arbitrary e = case e of
Foo -> 10
Bar -> 200
Baz -> 3000
Usage:
λ: map arbitrary [Foo ..]
[10,200,3000]
Nous venons de démontrer que cela fonctionnait bien, mais je préférerais le calculer à partir de la variable fromEnum
comme nous l'avons fait avec value
, si nous ne voulons pas que les valeurs augmentent de 1 à 0.