Есть ли способ определить Enum в haskell, который обертывается?
Считаю, что я разрабатывал игру в монополии:
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
Существует ли класс для выражения перечисления, которое обтекает?
Ответы
Ответ 1
Самый простой вариант - сделать Совет экземпляром 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/[email protected]/msg37258.html).
Если вам действительно нужно использовать succ
и pred
вместо этого, я не верю, что существуют какие-либо законы относительно реализаций Enum
, такие как succ (succ x) /= x
для всех x
(хотя именно так Работа). Поэтому вы можете просто написать пользовательскую реализацию 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
Более простое решение, чем нанометр:
nextBoard :: Board -> Board
nextBoard H2 = GO
nextBoard t = succ t
Я не думаю, что вы сможете использовать Enum напрямую для того, что хотите, но это решение быстро обертывает его, чтобы сформировать нужное поведение.
Ответ 3
Я знаю, что это старый вопрос, но у меня была эта проблема, и я решил это так.
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
.
Ответ 4
Существует отвратительный способ определить эффективный экземпляр 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 ....