Haskell枚举

dos*_*dos 8 haskell

我正在尝试为以下类型编写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)
Run Code Online (Sandbox Code Playgroud)

这适用于:

[(Pitch O1 A) .. (Pitch O3 F)]
Run Code Online (Sandbox Code Playgroud)

但失败了:

[(Pitch O1 A) .. ]
Run Code Online (Sandbox Code Playgroud)

有错误:

*** Exception: toEnum{Octave}: tag (3) is outside of enumeration's range (0,2)
Run Code Online (Sandbox Code Playgroud)

我理解错误.我的问题是:如何正确编写Enum实例来执行此枚举?可能吗?最重要的是:这是好的做法吗?

Ant*_*sky 9

你的问题是Pitch隐式限制的 - 也就是说,它有一个最小和最大的元素 - 但是你没有在代码中反映出这种有限性.代码

[Pitch O1 A ..]
Run Code Online (Sandbox Code Playgroud)

des to to to

enumFrom (Pitch O1 A)
Run Code Online (Sandbox Code Playgroud)

这保持succ荷兰国际集团所生成的值,直到它succš Pitch O3 F和鼓起.怎么会知道它应该停在那里?

来自Prelude文档:

对于任何类型的类的实例Bounded,以及Enum,下面应该成立:

...

因此,要解决此问题,只需添加

instance Bounded Pitch where
  minBound = Pitch minBound minBound
  maxBound = Pitch maxBound maxBound
Run Code Online (Sandbox Code Playgroud)

然后添加文档中的代码:

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
Run Code Online (Sandbox Code Playgroud)

现在[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]
Run Code Online (Sandbox Code Playgroud)

附注:您可以在一次调用中替换单独的调用divmod模式匹配divMod:x `divMod` y == (x `div` y, x `mod` y).(对于严格正数,像这些,我相信我听说这quotRem可能是一个更好的选择; quot并且remdivmod,但是有不同的符号相关行为.)另外,你可以用你的6s 代替1 + (fromEnum (maxBound :: Note)),以免意外得到这个数字错误.


Sin*_*ion 5

[x ..]desugared into enumFrom x方法,prelude提供以下默认实现:

enumFrom x = map toEnum [fromEnum x ..]
Run Code Online (Sandbox Code Playgroud)

覆盖它以通过您的实例做正确的事情.您也可能希望enumFromThen因类似原因而覆盖.