Better Way to Define an Enum in Haskell

I want a datatype to represent a finite set of integers that can be addressed by specific names. I figure the best way to do that is to use an Enum.

However, there is one small problem. The only way I know for defining an Enum is something like this:

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 

Note that I have to repeat the same pair two times - one time when defining an integer-to-enum mapping and the other time when defining an enum-to-integer mapping.

Is there a way to avoid this repetition?


data MyDataType = Foo | Bar | Baz deriving (Enum)

instance Enum MyDataType where
    fromEnum = fromJust . flip lookup table
    toEnum = fromJust . flip lookup (map swap table)
table = [(Foo, 0), (Bar, 1), (Baz, 2)]

The problem with the accepted solution is the compiler won't tell you when you are missing an enum in your table. The deriving Enum solution is great, but it won't work if you want to have an arbitrary mapping to numbers. Another answer suggests Generics or Template Haskell. This follows up on that by using 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

We will get compiler warning in the toNumber case mapping when a new constructor is added.

Now we just need the ability to turn that code into data so that the mapping can be automatically reversed. Here we generate the same table mentioned in the accepted solution.

table = map (\cData -> let c = (fromConstr cData :: MyDataType) in (c, toNumber c) )
      $ dataTypeConstrs $ dataTypeOf Foo

You can fill out an Enum class just the same as in the accepted answer. Unmentioned there is that you can also fill out the Bounded class.


Since you say the numbers are not generated by any regular law, you could use generic programming (e.g. with Scrap Your Boilerplate) or Template Haskell to implement a generic solution to this problem. I tend to prefer Template Haskell because it actually generates code and compiles it, so you get all the type-checking and optimisation benefits of GHC.

I wouldn't be surprised if someone had implemented this already. It should be trivial.