Reputation: 63
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
Reputation: 29
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
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
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