我正在尝试为以下类型编写Enum实例:
-- Type declarations:
-- Octave
data Octave =
O1 | O2 | O3
deriving (Show, Read, Eq, Ord, Bounded, Enum)
-- Note
data Note =
A | B | C | D | E | F
deriving (Show, Read, Eq, Ord, Bounded, Enum)
-- Pitch
data Pitch = Pitch Octave Note
deriving (Show, Eq, Ord)
-- Why doesn't this work?
instance Enum Pitch where
fromEnum (Pitch o n) = (fromEnum o)*6 + (fromEnum n)
toEnum x = (Pitch o n)
where
o = toEnum (x `div` 6)
n = toEnum (x `mod` 6)
这适用于:
[(Pitch O1 A) .. (Pitch O3 F)]
但是失败了:
[(Pitch O1 A) .. ]
错误:
*** Exception: toEnum{Octave}: tag (3) is outside of enumeration's range (0,2)
我理解错误。 我的问题是: 如何正确编写Enum实例来执行此枚举? 可能吗? 最重要的是:这是好的做法吗?
答案 0 :(得分:9)
您的问题是Pitch
隐含有界 - 即它具有最少且最大的元素 - 但您没有在代码中反映出这种有限性。代码
[Pitch O1 A ..]
desugars to
enumFrom (Pitch O1 A)
保持succ
生成的值,直到succ
s Pitch O3 F
并且爆炸为止。怎么会知道它应该停在那里?
From the Prelude documentation:
对于作为类
Bounded
的实例以及Enum
的任何类型,以下内容应该成立:...
应该定义
enumFrom
和enumFromThen
一个隐含的界限,因此:enumFrom x = enumFromTo x maxBound enumFromThen x y = enumFromThenTo x y bound where bound | fromEnum y >= fromEnum x = maxBound | otherwise = minBound
因此,要解决此问题,只需添加
即可instance Bounded Pitch where
minBound = Pitch minBound minBound
maxBound = Pitch maxBound maxBound
然后添加文档中的代码:
instance Enum Pitch where
-- ...
enumFrom x = enumFromTo x maxBound
enumFromThen x y = enumFromThenTo x y bound
where
bound | fromEnum y >= fromEnum x = maxBound
| otherwise = minBound
现在[Pitch O1 A ..]将在结束时停止:
λ> [Pitch O1 A ..]
[Pitch O1 A,Pitch O1 B,Pitch O1 C,Pitch O1 D,Pitch O1 E,Pitch O1 F,Pitch O2 A,Pitch O2 B,Pitch O2 C,Pitch O2 D,Pitch O2 E,Pitch O2 F,Pitch O3 A,Pitch O3 B,Pitch O3 C,Pitch O3 D,Pitch O3 E,Pitch O3 F]
附注:您可以通过对div
mod
的一次调用,将divMod
和x `divMod` y == (x `div` y, x `mod` y)
的单独调用替换为模式匹配。 (对于严格正数,我相信我听说quotRem
可能是更好的选择; quot
和rem
就像div
和{{1}但是,有不同的与行为相关的行为。)此外,您可以将mod
替换为6
,以避免误导错误。
答案 1 :(得分:5)
[x ..]
被移植到enumFrom x
方法中,prelude provides the following default implementation:
enumFrom x = map toEnum [fromEnum x ..]
覆盖它以通过您的实例执行正确的操作。出于类似原因,您也可能希望覆盖enumFromThen
。