Ответ 1
instance Enum MyDataType where
fromEnum = fromJust . flip lookup table
toEnum = fromJust . flip lookup (map swap table)
table = [(Foo, 0), (Bar, 1), (Baz, 2)]
Я хочу, чтобы тип данных представлял конечный набор целых чисел, которые могут быть адресованы конкретными именами. Я считаю, что лучший способ сделать это - использовать Enum
. Однако есть одна небольшая проблема. Единственный способ, которым я знаю для определения Enum, - это что-то вроде этого:
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
Обратите внимание, что я должен повторять одну пару два раза - один раз при определении отображения целочисленного числа и другого времени при определении отображения перечисления в целое. Есть ли способ избежать этого повторения?
Спасибо.
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)
Проблема с принятым решением - компилятор не скажет вам, когда вам не хватает перечисления в вашей таблице. Решение deriving Enum
отлично, но оно не будет работать, если вы хотите иметь произвольное сопоставление с числами. Другой ответ предлагает Generics или Template Haskell. Это следует за этим, используя 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
Мы получим предупреждение компилятора в сопоставлении case toNumber
при добавлении нового конструктора.
Теперь нам просто нужна возможность превратить этот код в данные, чтобы можно было автоматически перевернуть отображение. Здесь мы генерируем тот же table
, который указан в принятом решении.
table = map (\cData -> let c = (fromConstr cData :: MyDataType) in (c, toNumber c) )
$ dataTypeConstrs $ dataTypeOf Foo
Вы можете заполнить класс Enum
так же, как и в принятом ответе. Не указано, что вы также можете заполнить класс Bounded
.
Поскольку вы говорите, что цифры не генерируются никаким регулярным законом, вы можете использовать общее программирование (например, с помощью Scrap Your Boilerplate) или Template Haskell для реализации общего решения этой проблемы. Я предпочитаю Template Haskell, потому что он действительно генерирует код и компилирует его, поэтому вы получаете все преимущества проверки и оптимизации типов GHC.
Я бы не удивился, если бы кто-то уже реализовал это. Это должно быть тривиально.