我想要一种数据类型来表示可以通过特定名称寻址的有限整数集。我认为最好的方法是使用枚举。
但是,有一个小问题。我知道定义枚举的唯一方法是这样的:
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
请注意,我必须重复同一对两次 - 一次是在定义整数到枚举映射时,另一次是在定义枚举到整数映射时。
有没有办法避免这种重复?
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)]
已接受的解决方案的问题是,当您的表中缺少枚举时,编译器不会告诉您。
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
课程。
我这里的示例是使用带有提示的 GHCI 8.4.4,
"λ: "
。
我认为从
Enum
派生在这里最有意义,因为 Haskell 中最基本的类型也从 Enum
派生(元组、字符、整数等...),并且它具有获取值的 内置方法进出枚举。
首先,创建派生
Enum
的数据类型(和 Show
,以便您可以查看 REPL 中的值,并通过 Eq
启用 ..
范围补全):
λ: data MyDataType = Foo | Bar | Baz deriving (Enum, Show, Eq)
λ: [Foo ..]
[Foo,Bar,Baz]
fromEnum
,您可以使用它来获取问题中请求的值(0
、1
和 2
)。
用途:
λ: map fromEnum [Foo ..]
[0,1,2]
定义一个给出任意值的函数是一件简单的事情(例如使用整数幂运算符的 2 的幂,
^
):
λ: value e = 2 ^ (fromEnum e)
用途:
λ: map value [Foo ..]
[1,2,4]
另一个答案说:
解决方案很棒,但如果您想要任意映射到数字,它就不起作用。deriving Enum
好吧,让我们看看(如果您还没有启用 GHCI 中的多行输入,请使用
:set +m
):
arbitrary e = case e of
Foo -> 10
Bar -> 200
Baz -> 3000
用途:
λ: map arbitrary [Foo ..]
[10,200,3000]
我们刚刚证明了它确实有效,但如果我们不希望值从 0 增加 1,我更愿意像我们对
fromEnum
那样从 value
计算它。
既然你说这些数字不是由任何常规法则生成的,你可以使用通用编程(例如,使用 Scrap Your Boilerplate)或 Template Haskell 来实现此问题的通用解决方案。我倾向于更喜欢 Template Haskell,因为它实际上生成代码并编译它,因此您可以获得 GHC 的所有类型检查和优化优势。
如果有人已经实现了这个,我不会感到惊讶。这应该是微不足道的。
Deriving 库有几种不同的方法来创建枚举。
data MyEnum = Foo | Bar | Baz deriving (Enum, Show)
data MyColor = Red | Green | Blue | Yellow | Cyan | Magenta | White | Black deriving (Enum, Show)
-- Haskell Entry Point
-- To run this program, use: `ghc filename.hs` or `runhaskell filename.hs`
main = do
print Foo -- prints "Foo"
let enumColor = toEnum 4 :: MyColor
print enumColor -- prints "Cyan"
如果您使用 COTR Snippets VS Code 扩展,您可以使用
cotrEnum
代码片段在 Haskell 中创建枚举。