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.
It's true that the above examples of using const and iota are the most idiomatic ways of representing primitive enums in Go.
By keeping the enum in your database, and adding a foreign key on the table that contains an enum value you ensure that no code ever enters incorrect values for that column. This helps your data integrity and is the most obvious reason IMO you should have tables for enums.
-Dynamic enums are stored in java classes. -Static enums are stored in database.
An enum class can include methods and fields just like regular classes. When we create an enum class, the compiler will create instances (objects) of each enum constants. Also, all enum constant is always public static final by default.
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
data Card = Two | Three | Four | Five | Six
| Seven | Eight | Nine | Ten
| Jack | Queen | King | Ace
deriving Enum
Implementing the Enum
typeclass means you can use fromEnum
and toEnum
to convert between Card
and Int
.
However, if it's important to you that fromEnum Two
is 2
, you will have to implement the Enum
instance for Card
by hand. (The autoderived instance starts at 0
, just like C, but there's no way of overriding that without doing it all yourself.)
n.b. You might not need Enum
--- if all you want is to use operators like <
and ==
with your Card
s, then you need to use deriving Ord
.
Edit:
You cannot use read
to turn a String
of the form "2S"
or "QH"
into a (Card, Suit)
because read
will expect the string to look like "(a,b)"
(e.g. "(2,S)"
in the form you initially asked for, or "(Two,S)"
in the form I suggested above).
You will have to write a function to parse the string yourself. You could use a parser (e.g. Parsec or Attoparsec), but in this case it should be simple enough to write by hand.
e.g.
{-# 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With