问题
I have the following type definitions to represent cards:
data Suit = Hearts | Spades | Diamonds | Clubs
data Rank = Numeric Integer | Jack | Queen | King | Ace
data Card = Card Rank Suit
data Deck = None | Cons Card Deck
Numeric Integer represents ranks from 2 to 10.
Now I want to write a function to get the full deck:
fullDeck :: Deck
How could I generate the full deck in the most elegant way? I understand that some of these definitions are ugly but I have no freedom to choose this.
Thanks
回答1:
Does something like
-- Not sure why you're reinventing a list but
fromList :: [Card] -> Deck
fromList = foldr Cons None
fullDeck = fromList [Card r s | s <- [Hearts, Spades, Diamonds, Clubs]
, r <- map Numeric [2..9]++[Jack, Queen, King, Ace]]
Look nice? we're just using list comprehensions to generate a list of all possibilities than smashing it into a Deck
.
回答2:
You could consider using the universe package. It doesn't really make the code much easier, but it's fun. =)
Here's how it would look.
import Data.Universe
data Suit = Hearts | Spades | Diamonds | Clubs deriving (Bounded, Enum)
data Rank = Numeric Integer | Jack | Queen | King | Ace
instance Universe Suit
instance Universe Rank where
universe = map Numeric [2..10] ++ [Jack, Queen, King, Ace]
instance Finite Suit
instance Finite Rank
Now you can use universeF :: [(Rank, Suit)]
as your full deck of cards. If you really want custom Card
and Deck
types, you can add just a few lines of code:
data Card = Card Rank Suit
instance Universe Card where
universe = [Card rank suit | (rank, suit) <- universeF]
instance Finite Card
data Deck = None | Cons Card Deck
fullDeck = foldr Cons None universeF
回答3:
Quickly hacking around, came up with this:
data Suit = Hearts | Spades | Diamonds | Clubs deriving Show
data Rank = Numeric Integer | Jack | Queen | King | Ace deriving Show
data Card = Card Rank Suit deriving Show
data Deck = None | Cons Card Deck deriving Show
cards :: [Card]
cards = do
suit <- [Hearts, Spades, Diamonds, Clubs]
rank <- Ace : (map Numeric [2..10]) ++ [Jack, Queen, King]
return (Card rank suit)
listToDeck :: [Card] -> Deck
listToDeck [] = None
listToDeck (x:xs) = Cons x $ listToDeck xs
Is that elegant enough? :D
回答4:
@jozefg's answer is probably the easiest way, but an alternative that could make other operations easier is to defined Enum
and Bounded
instances for Suit
and Rank
:
data Suit = Hearts | Spades | Diamonds | Clubs deriving (Eq, Show, Enum, Bounded)
data Rank = Numeric Integer | Jack | Queen | King | Ace deriving (Eq, Show)
data Card = Card Rank Suit deriving (Eq, Show)
data Deck = None | Cons Card Deck deriving (Eq, Show)
instance Bounded Rank where
minBound = Numeric 2
maxBound = Ace
instance Enum Rank where
toEnum n
| n <= 1 = error "Invalid card value"
| n <= 10 = Numeric $ toInteger n
| n == 11 = Jack
| n == 12 = Queen
| n == 13 = King
| n == 14 = Ace
| otherwise = error "Invalid card value"
fromEnum (Numeric i) = fromEnum i
fromEnum Jack = 11
fromEnum Queen = 12
fromEnum King = 13
fromEnum Ace = 14
range :: (Bounded a, Enum a) => [a]
range = [minBound .. maxBound]
fromList :: [Card] -> Deck
fromList = foldr Cons None
fullDeck :: Deck
fullDeck = fromList [Card r s | r <- range, s <- range]
It's rather easy to let the compiler just derive the instances for Suit
, but it's a little more involved (but straightforward) for Rank
. Strictly speaking, you don't need range
, but I think it's kinda cool. Another advantage is that you can do things like [Numeric 2, Numeric 5, .. Ace]
and get back [Numeric 2, Numeric 5, Numeric 8, Jack, Ace]
.
EDIT
If you can't change the definition of the data types to add the deriving statements, you can do it like this:
{-# LANGUAGE StandaloneDeriving #-}
data Suit = Hearts | Spades | Diamonds | Clubs
-- etc
deriving instance Eq Suit
deriving instance Show Suit
deriving instance Enum Suit
deriving instance Bounded Suit
来源:https://stackoverflow.com/questions/19775249/list-data-type-haskell-with-bounds