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

问题描述 投票:0回答:6

我想要一种数据类型来表示可以通过特定名称寻址的有限整数集。我认为最好的方法是使用枚举。

但是,有一个小问题。我知道定义枚举的唯一方法是这样的:

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 

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

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

haskell enums
6个回答
69
投票
data MyDataType = Foo | Bar | Baz deriving (Enum)

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

18
投票

已接受的解决方案的问题是,当您的表中缺少枚举时,编译器不会告诉您。

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
课程。


4
投票

我这里的示例是使用带有提示的 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
计算它。


3
投票

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

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


0
投票

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 中创建枚举。

© www.soinside.com 2019 - 2024. All rights reserved.