我想要一种数据类型来表示可以通过特定名称寻址的有限整数集。我认为最好的方法是使用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
请注意,我必须重复两次相同的对 - 一次定义整数到枚举映射,另一次定义枚举到整数映射。
有没有办法避免这种重复?
答案 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,您可以使用该方法来获取问题中所要求的值(0
,1
和2
)。
用法:
λ: 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
计算出来。