2012-04-25 62 views
3

所以。我要代表一個類型的格式如下:Haskell去關於枚舉的方式

(Card, Suit) 

代表在紙牌遊戲卡在那裏Card情況下,將在集:

{2, 3, 4, 5, 6, 7, 8, 9, J, Q, K, 1} 

Suit會在設定的實例:

{S, D, H, C} 

我會處理這兩個數據的聲明,如果這是不是爲數字:

data Suit = S | D | H | C deri... 

但顯然增加數那些零元數的類型將失敗。

所以我的問題是,如何模擬你在C中找到的枚舉類型?

我想我misundestanding類型系統和幫助的一個基本觀點可以理解的!

編輯:我會添加一些上下文:我想要表示的數據包含在this Euler problem中,正如您可以檢查的,數據以1S形式表示爲一個鏟形王牌,2D表示一個鑽石形狀,等等......

我真正喜歡的是能夠直接對字符串進行讀取操作以獲得相應的對象。

+0

C也不能這樣做。 – 2012-04-25 10:02:05

+4

將「1S」或「2D」等字符串解析爲卡片的功能實際上與枚舉表示法是正交的。 – 2012-04-25 12:16:33

+0

@MatveyAksenov嗯,我明白這一點。我所尋找的是在類型系統中最習慣的方式。無論如何,我想我問了錯誤的問題。 – m09 2012-04-25 12:39:33

回答

17

實際上,從我開發撲克機器人的時候開始,我實際上已經有了一個實現。這不是特別複雜,但確實有效。

一,相關類型。秩和西裝是枚舉,而卡是明顯的複合型(使用自定義Show實例)

import Text.ParserCombinators.Parsec 

data Suit = Clubs | Diamonds | Hearts | Spades deriving (Eq,Ord,Enum,Show) 

data Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten 
      | Jack | Queen | King | Ace deriving (Eq,Ord,Enum,Show) 

data Card = Card { rank :: Rank 
       , suit :: Suit } deriving (Eq,Ord,Bounded) 

instance Show Card where 
    show (Card rank suit) = show rank ++ " of " ++ show suit 

然後我們分析代碼,它使用秒差距。你可以開發這個更復雜,返回更好的錯誤信息,等等。

請注意,正如馬特維在評論中所說,解析字符串到它們在程序中的表示的問題是(或者更確切地說應該是)與如何表示枚舉正交。在這裏,我欺騙並破壞了正交性:如果您想重新排列等級(例如Ace排名低於Two),那麼您會破壞解析代碼,因爲解析器取決於Two的內部表示形式,即0Three1等。

更好的方法是明確地說出parseRank中的所有等級(這就是我在原始代碼中所做的)。 (a)節省一些空間,(b)說明原則上如何將一個數字解析爲一個等級,以及(c)給出一個明確闡述的不良練習的例子,這樣就可以避免它在將來。

parseSuit :: Parser Suit 
parseSuit = do s <- oneOf "SDCH" 
       return $ case s of 
       'S' -> Spades 
       'D' -> Diamonds 
       'H' -> Hearts 
       'C' -> Clubs 

parseRank :: Parser Rank 
parseRank = do r <- oneOf "23456789TJQKA" 
       return $ case r of 
       'T' -> Ten 
       'J' -> Jack 
       'Q' -> Queen 
       'K' -> King 
       'A' -> Ace 
       n -> toEnum (read [n] - 2) 

parseCard :: Parser Card 
parseCard = do r <- parseRank 
       s <- parseSuit 
       return $ Card { rank = r, suit = s } 

readCard :: String -> Either ParseError Card 
readCard str = parse parseCard "" str 

這裏,它是在行動:

*Cards> readCard "2C" 
Right Two of Clubs 
*Cards> readCard "JH" 
Right Jack of Hearts 
*Cards> readCard "AS" 
Right Ace of Spades 

編輯:

@在你也許能有一些有趣的OverloadedStrings打的評論中提到yatima2975 。我沒有能夠做到這一點很有用,但看起來很有希望。首先,您需要啓用語言選項,方法是將{-# LANGUAGE OverloadedStrings #-}放在文件的頂部,幷包含行import GHC.Exts (IsString(..))以導入相關的類型類別。然後,你可以做一個Card成字符串文字:

instance IsString Card where 
    fromString str = case readCard str of Right c -> c 

這允許您模式匹配在您的信用卡的字符串表示,而不是明確地寫出來的類型:

isAce :: Card -> Bool 
isAce "AH" = True 
isAce "AC" = True 
isAce "AD" = True 
isAce "AS" = True 
isAce _ = False 

您也可以使用字符串文字作爲輸入功能:

printAces = do 
    let cards = ["2H", "JH", "AH"] 
    mapM_ (\x -> putStrLn $ show x ++ ": " ++ show (isAce x)) cards 

這裏,它是在行動:

*Cards> printAces 
Two of Hearts: False 
Jack of Hearts: False 
Ace of Hearts: True 
+0

感謝您的回答。快速提問:將卡類型設爲Read的實例會不會更好?我沒有使用parsec,所以我不知道是否忽略Read並將其解析到一邊是很常見的。 – m09 2012-04-25 12:56:56

+1

您可以添加一個派生的Read實例,儘管您只能獲得分析像「Card {rank = Two,suit = Clubs}」這樣的字符串的能力,而這些字符串不會有用。你可以自己指定一個'Read'實例,這基本上是爲你的類型編寫一個解析器。我不認爲有一種方法可以避免編寫比我在這裏做的更少的行。 – 2012-04-25 13:24:14

+1

現在使用「OverloadedStringLiterals」的選項是什麼?這會讓你非常接近DSL! – yatima2975 2012-04-25 15:07:28

8
data Card = Two | Three | Four | Five | Six 
      | Seven | Eight | Nine | Ten 
      | Jack | Queen | King | Ace 
    deriving Enum 

實施Enum類型類意味着你可以使用fromEnumtoEnumCardInt之間的轉換。

但是,如果對您很重要,fromEnum Two2,則必須親自實施Enum實例Card。 (自我驅動的實例從0開始,就像C一樣,但是如果不全部自己完成,沒有辦法覆蓋它。)

n.b.您可能不需要Enum ---如果你想要的是使用運營商如<==Card S,那麼你需要使用deriving Ord


編輯:

不能使用read打開窗體"2S""QH"String(Card, Suit)因爲read將期望字符串看起來像"(a,b)"(如"(2,S)"在您最初要求的形式,或者上面建議的形式"(Two,S)")。

您將不得不編寫一個函數來自己分析字符串。您可以使用解析器(例如Parsec或Attoparsec),但在這種情況下,它應該足夠簡單,可以手動編寫。

例如

{-# LANGUAGE TupleSections #-} 

parseSuit :: String -> Maybe Suit 
parseSuit "S" = Just S 
... 
parseSuit _ = Nothing 

parseCard :: String -> Maybe (Card, Suit) 
parseCard ('2' : s) = fmap (Two,) (parseSuit s) 
... 
parseCard _   = Nothing 
+0

@Mod這個答案實際上已經解決了你的編輯問題。 – 2012-04-25 12:19:51

+0

@Mog看我的編輯。 – dave4420 2012-04-25 12:27:59

1

我只是在數字前面添加一個字母或者更好的單詞。我也不會使用太多的單字母縮寫 - HK等是完全不可讀的。

data Suit = Club | Spade | Heart | Diamond 
data Card = Card1 | Card2 | … | Jack | Queen | King | Ace 

...但我甚至寧願使用值數字(OneTwo),而不是大衛的建議。