在 Haskell 中定义枚举的更好方法
我想要一个数据类型来表示可以通过特定名称寻址的有限整数集。我认为最好的方法是使用枚举。
然而,有一个小问题。我知道定义枚举的唯一方法是这样的:
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
请注意,我必须重复同一对两次 - 一次是在定义整数到枚举映射时,另一次是在定义枚举到整数映射时。
有没有办法避免这种重复呢?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
已接受解决方案的问题是,当您的表中缺少枚举时,编译器不会告诉您。
派生 Enum
解决方案很棒,但如果您想要任意映射到数字,它就不起作用。另一个答案建议泛型或模板 Haskell。在此基础上使用Data
。添加新构造函数时,我们将在
toNumber
案例映射中收到编译器警告。现在我们只需要能够将该代码转换为数据,以便可以自动反转映射。在这里,我们生成与接受的解决方案中提到的相同的
表
。您可以填写一个
Enum
类,就像在接受的答案中一样。没有提到的是,您还可以填写Bounded
类。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 usingData
.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.You can fill out an
Enum
class just the same as in the accepted answer. Unmentioned there is that you can also fill out theBounded
class.我这里的示例是使用 GHCI 8.4.4 并带有提示符
"λ: "
。我认为从 Enum 派生在这里最有意义,因为 Haskell 中最基本的类型也从 Enum 派生(元组、字符、整数等),并且它有 内置方法值传入和传出枚举。
首先,创建一个派生
Enum
的数据类型(和Show
),以便您可以查看 REPL 和Eq
中的值以启用..
范围补全):枚举定义一个方法,
fromEnum
,您可以使用它来获取问题中请求的值(0
、1
和 <代码>2)。用法:
定义一个给出任意值的函数是一件简单的事情(例如使用整数幂运算符
^
):用法:
另一个答案说:
好吧,让我们看看(如果您还没有在 GHCI 中启用多行输入,请使用
:set +m
):用法:
我们刚刚证明它确实有效,但我更愿意如果我们不希望值从 0 开始增加 1,请像使用
value
一样从fromEnum
计算它。My examples here are using GHCI 8.4.4 with a prompt,
"λ: "
.I think deriving from
Enum
makes the most sense here, as the most fundamental types in Haskell also derive fromEnum
(tuples, characters, integers, etc...), and it has builtin methods of getting values into and from the enum.First, create a data type deriving
Enum
(andShow
so you can view the value in the REPL andEq
to enable..
range completion):Enums define a method,
fromEnum
, which you can use to get the values as requested in the question (0
,1
, and2
).Usage:
It is a simple matter to define a function giving an arbitrary value (such as powers of two using the integer power operator,
^
):Usage:
Another answer says:
Well, let's see about that (use
:set +m
to enable multiline input in GHCI, if you haven't already):Usage:
We just demonstrated that it does indeed work, but I would prefer to calculate it from the
fromEnum
as we did withvalue
, if we do not want values increasing by 1 from 0.既然你说这些数字不是由任何常规法则生成的,那么你可以使用通用编程(例如使用 Scrap Your Boilerplate)或 Template Haskell 来实现此问题的通用解决方案。我倾向于更喜欢 Template Haskell,因为它实际上生成代码并编译它,因此您可以获得 GHC 的所有类型检查和优化优势。
如果有人已经实现了这个,我不会感到惊讶。这应该是微不足道的。
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.