I want to represent a type of the following form :
(Card, Suit)
to represent cards in a card game where Card
instances would be in the set:
{2, 3, 4, 5, 6, 7, 8, 9, J, Q, K, 1}
and Suit
would have instances in the set:
{S, D, H, C}
I'd handle that with two Data declarations if that wasn't for the numbers:
data Suit = S | D | H | C deri...
but obviously adding numbers to those null arity types will fail.
So my question is, how to simulate the kind of enum you find in C?
I guess I'm misundestanding a basic point of the type system and help will be appreciated!
EDIT: I'll add some context: I want to represent the data contained in this Euler problem, as you can check, the data is represented in the form of 1S for an ace of spade, 2D for a 2 of diamond, etc...
What I'd really like is to be able to perform a read operation directly on the string to obtain the corresponding object.
I actually happen to have an implementation handy from when I was developing a poker bot. It's not particularly sophisticated, but it does work.
First, the relevant types. Ranks and suits are enumerations, while cards are the obvious compound type (with a custom Show
instance)
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
Then we have the parsing code, which uses Parsec. You could develop this to be much more sophisticated, to return better error messages, etc.
Note that, as Matvey said in the comments, the problem of parsing strings into their representations in the program is (or rather should be) orthogonal to how the enums are represented. Here I've cheated and broken the orthogonality: if you wanted to re-order the ranks (e.g. to have Ace
rank below Two
) then you would break the parsing code, because the parser depends on the internal representation of Two
being 0
, Three
being 1
etc..
A better approach would be to spell out all of the ranks in parseRank
explicitly (which is what I do in the original code). I wrote it like this to (a) save some space, (b) illustrate how it's possible in principle to parse a number into a rank, and (c) give you an example of bad practice explicitly spelled out, so you can avoid it in the future.
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
And here it is in action:
*Cards> readCard "2C"
Right Two of Clubs
*Cards> readCard "JH"
Right Jack of Hearts
*Cards> readCard "AS"
Right Ace of Spades
Edit:
@yatima2975 mentioned in the comments that you might be able to have some fun playing with OverloadedStrings
. I haven't been able to get it to do much that's useful, but it seems promising. First you need to enable the language option by putting {-# LANGUAGE OverloadedStrings #-}
at the top of your file, and include the line import GHC.Exts ( IsString(..) )
to import the relevant typeclass. Then you can make a Card
into a string literal:
instance IsString Card where
fromString str = case readCard str of Right c -> c
This allows you to pattern-match on the string representation of your card, rather than having to write out the types explicitly:
isAce :: Card -> Bool
isAce "AH" = True
isAce "AC" = True
isAce "AD" = True
isAce "AS" = True
isAce _ = False
You can also use the string literals as input to functions:
printAces = do
let cards = ["2H", "JH", "AH"]
mapM_ (\x -> putStrLn $ show x ++ ": " ++ show (isAce x)) cards
And here it is in action:
*Cards> printAces
Two of Hearts: False
Jack of Hearts: False
Ace of Hearts: True