2012-04-25 7 views
3

So. Cardインスタンスがセットになり、カードゲームでカードを表現するためにハスケル・エニュームの方法について

(Card, Suit) 

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

Suitがセット内のインスタンスを持つことになり、私は次の形式の種類を表現したいです。それは数字のためではなかった場合

{S, D, H, C} 

私は2つのデータ宣言でそのハンドルたい:

明らかに、これらの空値タイプに数値を加算することは失敗する。

私の質問は、どのようにCで見つけるenumの種類をシミュレートするのですか?

私はタイプシステムの基本的な点を誤解しており、助けになると思います!

編集:私はthis Euler problemに含まれるデータを表現したいと思うので、データはスペードのエースの場合は1S、ダイヤモンドの場合は2次元、

私が本当に好きなのは、対応するオブジェクトを取得するために文字列に直接読み込み操作を実行できるようにすることです。 Enum型クラスの実装

+0

Cでもできません。 –

+4

"1S"や "2D"のような文字列をカードに解析する機能は、実際には列挙型表現と直交しています。 –

+0

@MatveyAksenovまあ、私はそれを理解しています。私が探していたのは、型システム内でそれを行うための最も慣用的な方法でした。とにかく、私は間違った質問をしたと思います。 – m09

答えて

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 

ありながら、我々はParsecのを使用して解析コードを、持っている、列挙されています。これを開発して、より洗練されたエラーメッセージなどを返すことができます。

コメントにMatveyが述べたように、文字列をプログラムで表現する際の問題は、)を列挙しています。ここで私は不正行為をして直交性を壊してしまった:のランクを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(..))という行を含めて言語オプションを有効にして、関連するtypeclassをインポートする必要があります。あなたは文字列リテラルに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をまだ使用していないので、読み書きを無視してそれを脇に解析するのが一般的かどうかわかりません。 – m09

+1

"Card {rank = Two、suit = Clubs}"のような文字列を解析する能力しか得られませんが、Readの派生インスタンスを追加すると便利です。基本的にあなたのタイプのパーサーを書くことになる、 'Read'インスタンスを自分で指定することができます。私は、ここでやったよりも少ない行を書こうとする方法がないとは思わない。 –

+1

'OverloadedStringLiterals'を使用するオプションは何ですか?それはあなたをDSLにかなり近づけるでしょう! – yatima2975

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

あなたはCardIntの間で変換することfromEnumtoEnumを使用できることを意味します。

fromEnum Two2の場合は、手でCardEnumインスタンスを実装する必要があります。 (オートドリブンのインスタンスはCのように0から始まりますが、それをすべて自分で実行することなくオーバーライドする方法はありません)。

n.b. Enum --- <==のような演算子をCardと組み合わせて使用​​する場合は、deriving Ordを使用する必要があります。


編集:

あなたは文字列が最初にのために尋ねた形で例えば"(a,b)""(2,S)"のように見えることを期待します(Card, Suit)readためにフォーム"2S"または"QH"Stringをオンにするreadを使用することはできません、または"(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この回答は、実際にあなたの編集に既に対応しています。 –

+0

@Mog私の編集を参照してください。 – dave4420

1

私は、数字に文字またはそれ以上の単語を付けるだけです。また、あまりにも多くの1文字の略語を使用しないでください。HKなどはまったく読めません。

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

...しかし、私も代わりの値の数単語(OneTwo)を使用してのDaveの提案を好みます。