Fraus
Fraus

Reputation: 63

Why can't I write the function like this?

I am studying for an "Introduction to Functional Programming" exam. This is one of the problems I am stuck on:

"The following datatypes are used to represent a hand of cards:

data Suit = Hearts | Clubs | Diamonds | Spades                           
  deriving Eq

data Rank = Numeric Int | Jack | Queen | King | Ace                      
   deriving Eq

data Card = NormalCard Rank Suit | Joker   
  deriving Eq

Define a function

countAces:: [Card] -> Int
countAces = undefined

where countAces returns the number of cards in the given hand which are either aces or jokers. So for example, if there are three aces and two jokers in the hand, the answer will be five."

So I thought I would write it like this:

countAces:: [Card] -> Int
countAces []                               = 0
countAces (c:cs) | c == (NormalCard Ace _) = 1 + countAces (cs)
                 | c == Joker              = 1 + countAces (cs)
                 | otherwise               = countAces (cs)

But this won't compile, and I have understood that I can't write c == (NormalCard Ace _). But if I change the function to:

countAces:: [Card] -> Int
countAces []                         = 0
countAces (c : cs) = countCard c + countAces cs
  where countCard Joker              = 1
        countCard (NormalCard Ace _) = 1
        countCard _                  = 0

Then it works! So my question is, why doesn't the first version work?

This is the error:

    * Found hole: _ :: Suit
    * In the second argument of `NormalCard', namely `_'
      In the second argument of `(==)', namely `(NormalCard Ace _)'
      In the expression: c == (NormalCard Ace _)
    * Relevant bindings include
        cs :: [Card] (bound at exam.hs:96:14)
        c :: Card (bound at exam.hs:96:12)
        countAces :: [Card] -> Int (bound at exam.hs:95:1)
      Valid substitutions include
        Hearts :: Suit (defined at exam.hs:87:13)
        Clubs :: Suit (defined at exam.hs:87:22)
        Diamonds :: Suit (defined at exam.hs:87:30)
        Spades :: Suit (defined at exam.hs:87:41)
        undefined :: forall (a :: TYPE r).
                     GHC.Stack.Types.HasCallStack =>
                     a
          (imported from `Prelude' at exam.hs:1:1
           (and originally defined in `GHC.Err'))

Thanks a ton to anyone who took the time to read this.

Upvotes: 1

Views: 243

Answers (3)

Without going through the manuals, my guess is that with "==" you are comparing values and here what you is different structures. In the second solution you are using pattern matching against the structure.

When you define an abstract data type in Haskell with alternative structures "deriving Eq" is not enough in order to compare those different structures, you will have to provide a definition for the equality. You use "==" in the way you put it in the first case as long as define "==" over two Card elements.

Upvotes: -1

fdreger
fdreger

Reputation: 12505

This question is more about your mental model of what things mean, not about Haskell itself (if you ask about Haskell itself, then the answer is: "because that's how the language is works").

So I will try to just appeal to your imagination:

In the first case you have an expression, which will evaluate to True or False - both are valid outcomes. You are performing a comparison, using an existing function: (==). This function takes two values - and you need to provide them fully, without holes - for exactly the same reason, why you can't write (2 + _) * 10 and expect it to evaluate to a number.

In the second case, you use a language construct =. This construct is not a function that returns a value. It is used to build a definition. When you write a = 2, you are not writing an expression which can be true or false. You are defining a in terms of 2. It will either work and be forever true - or will not compile. In this context - you can use holes. When you write a _ = 2, you really say: No matter what you apply to a you will get 2.

Upvotes: 5

chepner
chepner

Reputation: 532323

In your guard c == (NormalCard Ace _), you aren't performing a pattern match; you are trying to compare c to a new value of Card whose suit you haven't specified. The _ is a "hole", which is a non-value that triggers an error but determines what type the _ should have, which is useful for debugging and development.

To do the pattern match, use an explicit case expression:

countAces (c:cs) = case c of
                     (NormalCard Ace _) -> 1 + countAces cs
                     Joker -> 1 + countAces cs
                     otherwise -> countAces cs

Because case is an expression, not a statement, you can refactor this to reduce some repetition:

countAces (c:cs) = countAces cs + case c of
                      (NormalCar Ace _) -> 1
                      Joker -> 0
                      otherwise -> 0

which basically inlines the countCard function you defined in your second attempt.

Upvotes: 7

Related Questions