我想代表以下形式的一种类型:
(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...
但是很明显,将数字添加到这些空Arity类型将失败。
所以我的问题是,如何模拟在C中找到的枚举类型?
我想我会误解类型系统的基本要点,将不胜感激!
编辑:我要添加一些上下文:我要表示this Euler problem中包含的数据,正如您可以检查的那样,数据以1S表示黑桃A,2D表示2菱形,等等...
我真正想要的是能够直接在字符串上执行读取操作以获得相应的对象。
实际上,从我开发扑克机器人开始,我就很方便地实现了实现。它不是特别复杂,但确实可以工作。
首先是相关类型。等级和花色是枚举,而牌是明显的复合类型(带有自定义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
),那么您将破坏解析代码,因为解析器取决于[ C0]为Two
,0
为Three
等。一种更好的方法是显式地拼出1
中的所有等级(这是我在原始代码中所做的事情)。我这样写是为了(a)节省一些空间,(b)说明原则上如何将数字解析为等级,并且(c)给出一个明确阐明错误做法的示例,因此您可以避免它将来。
parseRank
这里正在起作用:
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
@ yatima2975在评论中提到,使用编辑:
*Cards> readCard "2C"
Right Two of Clubs
*Cards> readCard "JH"
Right Jack of Hearts
*Cards> readCard "AS"
Right Ace of Spades
可能会有些有趣。我无法让它做很多有用的事情,但似乎很有希望。首先,您需要通过将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
H
,K
等完全无法读取。