考虑一下我在设计一款大富翁游戏:
data Board = GO | A1 | CC1 | A2 | T1 | R1 | B1 | CH1 | B2 | B3 |
JAIL | C1 | U1 | C2 | C3 | R2 | D1 | CC2 | D2 | D3 |
FP | E1 | CH2 | E2 | E3 | R3 | F1 | F2 | U2 | F3 |
G2J | G1 | G2 | CC3 | G3 | R4 | CH3 | H1 | T2 | H2
deriving (Show, Enum, Eq)
我想:
succ H2 == GO
但相反:
*** Exception: succ{Board}: tried to take `succ' of last tag in enumeration
是否存在表达枚举的类型类?
答案 0 :(得分:27)
比nanothief更简单的解决方案:
nextBoard :: Board -> Board
nextBoard H2 = GO
nextBoard t = succ t
我认为你不能直接将Enum用于你想要的东西,但是这个解决方案会快速包装它以形成你想要的行为。
答案 1 :(得分:20)
最简单的选择是使Board成为Bounded
的实例(也可以自动派生),并使用以下辅助函数:
next :: (Enum a, Bounded a) => a -> a
next = turn 1
prev :: (Enum a, Bounded a) => a -> a
prev = turn (-1)
turn :: (Enum a, Bounded a) => Int -> a -> a
turn n e = toEnum (add (fromEnum (maxBound `asTypeOf` e) + 1) (fromEnum e) n)
where
add mod x y = (x + y + mod) `rem` mod
使用示例:
> next H2
G0
> prev G0
H2
> next F1
F2
(灵感来自http://www.mail-archive.com/haskell-cafe@haskell.org/msg37258.html的主题。)
如果您确实需要使用succ
和pred
,我认为没有关于Enum
的实施的法律,succ (succ x) /= x
适用于所有x
1}}(即使这是大多数工作)。因此,您可以为您的类型编写Enum
的自定义实现,展示您想要的环绕声:
instance Enum Board where
toEnum 0 = G0
toEnum 1 = A1
...
toEnum 40 = H2
toEnum x = toEnum (x `mod` 40)
fromEnum G0 = 0
fromEnum A1 = 1
...
fromEnum H2 = 40
但实施起来非常繁琐。此外,在使用Bounded
的循环定义时,该类型也不应该实现Enum
,因为这违反了Bounded
succ maxBound
应该导致运行时错误的规则。
答案 2 :(得分:5)
我知道这是一个老问题,但我遇到了这个问题,我就这样解决了。
data SomeEnum = E0 | E1 | E2 | E3
deriving (Enum, Bounded, Eq)
-- | a `succ` that wraps
succB :: (Bounded a, Enum a, Eq a) => a -> a
succB en | en == maxBound = minBound
| otherwise = succ en
-- | a `pred` that wraps
predB :: (Bounded a, Enum a, Eq a) => a -> a
predB en | en == minBound = maxBound
| otherwise = pred en
解决方案同时派生Enum
和Bounded
,但避免按建议滥用pred
和succ
。
我很快就找到了
allSomeEnum = [minBound..maxBound] :: [SomeEnum]
非常有用。这需要Bounded
。
答案 3 :(得分:3)
有一种令人作呕的方法来定义一个有效的包装Enum
实例,而无需手工操作。
{-# LANGUAGE MagicHash #-}
import GHC.Exts (Int (..), tagToEnum#, dataToTag# )
-- dataToTag# :: a -> Int#
-- tagToEnum# :: Int# -> a
现在你可以写
了data Board = ... deriving (Eq, Ord, Bounded)
instance Enum Board where
fromEnum a = I# (dataToTag# a)
toEnum x | x < 0 || x > fromEnum (maxBound :: Board) =
error "Out of range"
toEnum (I# t) = tagToEnum# t
succ x | x == maxBound = minBound
| otherwise == toEnum (fromEnum x + 1)
pred x ....
答案 4 :(得分:0)
使用Eq
,您可以检查它是否是最后一个元素。
next :: (Eq a, Enum a, Bounded a) => a -> a
next = bool minBound <$> succ <*> (/= maxBound)