在Haskell中定义枚举的更好方法

时间:2011-05-14 07:47:35

标签: haskell enums

我想要一种数据类型来表示可以通过特定名称寻址的有限整数集。我认为最好的方法是使用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 

请注意,我必须重复两次相同的对 - 一次定义整数到枚举映射,另一次定义枚举到整数映射。

有没有办法避免这种重复?

5 个答案:

答案 0 :(得分:49)

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

答案 1 :(得分:32)

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

答案 2 :(得分:13)

接受的解决方案的问题是编译器在您丢失表中的枚举时不会告诉您。 deriving Enum解决方案非常棒,但如果您想要对数字进行任意映射,它就无法工作。另一个答案表明泛型或模板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

添加新构造函数时,我们将在toNumber案例映射中收到编译器警告。

现在我们只需要能够将代码转换为数据,以便映射可以自动反转。在这里,我们生成了在已接受的解决方案中提到的相同table

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

您可以按照接受的答案填写Enum课程。未提及您还可以填写Bounded课程。

答案 3 :(得分:3)

由于您说这些数字不是由任何常规法律生成的,您可以使用泛型编程(例如使用Scrap Your Boilerplate)或Template Haskell来实现此问题的通用解决方案。我更倾向于使用模板Haskell,因为它实际上生成代码并编译它,因此您可以获得GHC的所有类型检查和优化优势。

如果有人已经实现了这一点,我不会感到惊讶。这应该是微不足道的。

答案 4 :(得分:1)

我在这里的示例使用的是带有提示"λ: "的GHCI 8.4.4。

我认为从Enum派生在这里最有意义,因为Haskell中最基本的类型也从Enum派生(元组,字符,整数等),并且它具有{ {3}}在枚举中获取值。

首先,创建派生自Enum(和Show的数据类型,以便您可以在REPL和Eq中查看值以启用..范围补全):

λ: data MyDataType = Foo | Bar | Baz deriving (Enum, Show, Eq)
λ: [Foo ..]
[Foo,Bar,Baz]

枚举定义了一种方法builtin methods,您可以使用该方法来获取问题中所要求的值(012)。

用法:

λ: map fromEnum [Foo ..]
[0,1,2]

定义一个给出任意值的函数(例如使用整数幂运算符^的2的幂)很简单:

λ: value e = 2 ^ (fromEnum e)

用法:

λ: map value [Foo ..]
[1,2,4]

另一个答案是:

  

deriving Enum解决方案很棒,但是如果您想对数字进行任意映射,它将无法正常工作。

好吧,让我们看看(如果尚未使用,请使用:set +m在GHCI中启用多行输入):

arbitrary e = case e of
  Foo -> 10
  Bar -> 200
  Baz -> 3000

用法:

λ: map arbitrary [Foo ..]
[10,200,3000]

我们只是证明了它确实可以工作,但是,如果我们不希望值从0开始增加1,我宁愿像使用fromEnum一样从value计算出来。