私は実際、ポーカーボットを開発していたときから実装が便利でした。特に洗練されているわけではありませんが、機能します。
まず、関連するタイプ。ランクとスーツは列挙型ですが、カードは明らかな複合タイプです(カスタム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
次に、Parsecを使用する解析コードがあります。これを開発して、より洗練されたものにしたり、より適切なエラーメッセージを返したりすることができます。
Matveyがコメントで述べたように、プログラム内の表現に文字列を解析する問題は、列挙型の表現方法と直交している(またはそうであるべきである)ことに注意してください。ここで私は直交性をだまして壊しました:ランクを並べ替えたい場合(たとえば、Ace
ランクを下にする場合)、パーサーは存在、存在などTwo
の内部表現に依存するため、構文解析コードを壊します。Two
0
Three
1
より良いアプローチは、すべてのランクを明示的に綴ることです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
編集:
@ yatima2975はコメントで、で遊んで楽しむことができるかもしれないと述べていますOverloadedStrings
。私はそれを多くの有用なことをするようにさせることができませんでした、しかしそれは有望であるように思われます。{-# 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