簡體   English   中英

有沒有辦法在haskell中定義一個環繞的枚舉?

[英]Is there some way to define an Enum in haskell that wraps around?

考慮我正在設計一個大富翁游戲:

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

是否有用於表示環繞的枚舉的類型類?

比nanothief更簡單的解決方案:

nextBoard :: Board -> Board
nextBoard H2 = GO
nextBoard t = succ t

我認為您不能直接將Enum用於所需的內容,但是此解決方案可以快速將其包裝以形成所需的行為。

最簡單的選擇是使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上的線程的啟發)。

如果您真的需要使用succpred ,我不認為有任何關於Enum實現的法律,例如對所有x都使用succ (succ x) /= x x (即使多數情況下也是這樣)。 因此,您可以為您的類型編寫一個自定義的Enum實現,以實現所需的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

但是,實現起來非常繁瑣。 此外,在使用Enum的循環定義時,該類型也不應實現Bounded ,因為這違反了有關Bounded的規則,即succ maxBound導致運行時錯誤。

我知道這是一個古老的問題,但是我只是遇到了這個問題,所以就這樣解決了。

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但是避免按建議濫用predsucc

偶然地,我發現

allSomeEnum = [minBound..maxBound] :: [SomeEnum] 

可能會有用。 那需要Bounded

有一種令人作嘔的方式來定義高效的包裝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 ....

您可以在下面定義類型類和實例。 你只需要推導出Bounded

class (Eq a, Enum a, Bounded a) => CyclicEnum a where
  cpred :: a -> a
  cpred d
    | d == minBound = maxBound
    | otherwise = pred d
  
  csucc :: a -> a
  csucc d
    | d == maxBound = minBound
    | otherwise = succ d
    

instance CyclicEnum Board

這個例子來自一本神奇的書: Haskell in Depth

使用Eq您可以檢查它是否為最后一個元素。

next :: (Eq a, Enum a, Bounded a) => a -> a
next = bool minBound <$> succ <*> (/= maxBound)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM