Home > Blockchain >  What are the more idiomatic ways of constructing this datatype?
What are the more idiomatic ways of constructing this datatype?

Time:11-23

I'm creating a card game in Haskell where I need to know the color of the card for each suit.

Right now I construct all possible combinations of suit, rank, and color using cardCombinations and then filter the result with cardWithColors to construct the Deck

The requirement is I want the suits to have the correct associated color: Diamonds, Hearts are Red and Spades, Clubs are Black.

Is there possibly a more concise way to express this logic rather than first constructing all combinations and then filtering them?

module Deck (Suit,Color,Rank,Card,Deck) where

data Color = Red | Black deriving (Eq,Enum,Show,Ord,Bounded)

data Suit = Clubs | Diamonds | Hearts | Spades deriving (Eq,Ord,Enum,Bounded,Show)

data Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten
          | Jack | Queen | King | Ace deriving (Eq,Ord,Enum,Bounded,Show)  

data Card = Card { rank :: Rank
                 , suit :: Suit
                 , color :: Color } deriving (Eq,Ord,Bounded,Show)


type Deck = [Card]

findColorForSuit :: Suit -> Color 
findColorForSuit suit 
    | any (suit==) [Diamonds, Hearts] = Red
    | any (suit==) [Spades, Clubs] = Black

cardCombinations = [(ranks, suits, colors) | ranks <- [minBound :: Rank .. maxBound :: Rank], suits <- [minBound :: Suit .. maxBound :: Suit], colors <- [minBound :: Color .. maxBound :: Color]]


cardsWithColors = filter (\(_,suit,color) -> (findColorForSuit suit) == color) cardCombinations

makeCard :: (Rank, Suit, Color) -> Card 
makeCard (rank, suit, color) = Card rank suit color 

makeCards :: Deck
makeCards = map makeCard cardsWithColors

I tried creating all combinations and then filtering the result based off a filter predicate to determine how to associate the color to the suit.

CodePudding user response:

These requirement are also known as an invariant and the idiomatic way is to avoid them if possible (make invalid states impossible). Sometimes it is really hard and/or not practical to avoid all the invariants, however in your case it is really easy because the Color of each card is uniquely determined by the Suit. Instead of the color field you could just define it as a function.

data Card = Card { rank :: Rank, suit :: Suit }
  deriving (..)

color :: Card -> Color
color card = findColorForSuit (suit card)
  

This means that each Card is a valid Card by construction and you can just construct all possible combinations of ranks and suits.

CodePudding user response:

I would make Card only have the rank and suit, and then write a function that calculates the colour (by using your findColourForSuit, although this will be even simpler if you use a single pattern match, instead of using any, ==, and lists).

You don't need to store the colour of every card, it's implied by the suit that you're already storing. In much the same way as we don't put a field even :: Bool on every Integer; we just store the integer's value, and when we need to know whether it's odd or even we calculate that.

  • Related