functionhaskelllist-comprehensionhaskell-platform

How to transform a list of enums to another type of enum in Haskell?


I'm trying to write a function that creates a full deck of cards (52 with no Jokers). I'm still new to this, I'm thinking that some sort of list comprehension is what I need to do, but I don't know quite how to write it (see createHandFromList function). I'm also guessing it needs to be recursive so that it will keep going. Anyway, here's what my code looks like so far, any help and advice is, as always, much appreciated.

data Card = Card Rank Suit
      deriving (Eq, Show)

data Rank = Numeric Integer | Jack | Queen | King | Ace
            deriving (Eq, Show)

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

data Hand = Empty | Add Card Hand
            deriving (Eq, Show)

fullDeck :: Hand
fullDeck = createHandFromList (suitedCardList Clubs ++
                suitedCardList Diamonds ++
                suitedCardList Hearts ++
                suitedCardList Spades)

suitedCardList :: Suit -> [Card]
suitedCardList s = [Card (Numeric 2) s, Card (Numeric 3) s,
                    Card (Numeric 4) s, Card (Numeric 5) s,
                    Card (Numeric 6) s, Card (Numeric 7) s,
                    Card (Numeric 8) s, Card (Numeric 8) s,
                    Card (Numeric 10) s, Card Jack s,
                    Card Queen s, Card King s, Card Ace s]

createHandFromList :: [Card] -> Hand -> Hand
createHandFromList [c:cs] h = [Add card h | card <- c]

Currently, the code returns this as an error:

BlackJack.hs:107:21: error:
    • Couldn't match expected type ‘Card’ with actual type ‘[[Card]]’
    • In the pattern: c : cs
      In the pattern: [c : cs]
      In an equation for ‘createHandFromList’:
          createHandFromList [c : cs] h = [Add card h | card <- c]
    |
107 | createHandFromList [c:cs] h = [Add card h | card <- c]     |                     ^^^^

BlackJack.hs:107:31: error:
    • Couldn't match expected type ‘Hand’ with actual type ‘[Hand]’
    • In the expression: [Add card h | card <- c]
      In an equation for ‘createHandFromList’:
          createHandFromList [c : cs] h = [Add card h | card <- c]
    |
107 | createHandFromList [c:cs] h = [Add card h | card <- c]     | 

Solution

  • There are multiple problems with this code. Firstly, you have used [c:cs] as a pattern in the definition of createHandFromList. However, this is a syntax error: in pattern matching, the syntax is (c:cs). This gives the following code:

    createHandFromList :: [Card] -> Hand -> Hand
    createHandFromList (c:cs) h = [Add card h | card <- c]
    

    But there is still another problem with this code. You return a list of Hands, but the type signature says that you must return a single Hand! Clearly this is wrong. To figure out what to do instead, let’s take a step back and work through exactly what createHandFromList should do:

    This can be done most easily with recursion:

    createHandFromList :: [Card] -> Hand -> Hand
    -- Add card ‘c’ to the result of (createHandFromList cs h)
    createHandFromList (c:cs) h = Add c (createHandFromList cs h)
    -- Return hand ‘h’ if there are no more cards left to add
    createHandFromList [] h = h
    

    There is a small simplification I want to address as well. (Credit goes to @Khuldraeseth na'Barya, who wrote this in the comments.) Let’s look at the possible values of Hand:

    This looks exactly like a list of Cards! And it turns out that the types Hand and [Card] are isomorphic. In pseudo-Haskell:

    data Hand   = Empty | Add Card Hand
    data [Card] = []    | :   Card [Card]
    

    So, if you represent Hand as something like newtype Hand = Hand { getCards :: [Card] }, then you should be able to replace the recursion by a list comprehension. This is left as an exercise for the reader.