我正在为“函数式编程简介”考试学习。 这是我遇到的问题之一:
“以下数据类型用于表示一副牌:
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
定义一个函数
countAces:: [Card] -> Int
countAces = undefined
其中countAces返回给定手牌中的A数或Joker数。因此,例如,如果手中有三个ace和两个小丑,答案将是五个。“
所以我想我会这样写:
countAces:: [Card] -> Int
countAces [] = 0
countAces (c:cs) | c == (NormalCard Ace _) = 1 + countAces (cs)
| c == Joker = 1 + countAces (cs)
| otherwise = countAces (cs)
但是这不会编译,而且我了解我不能写c ==(NormalCard Ace _)。但是,如果我将功能更改为:
countAces:: [Card] -> Int
countAces [] = 0
countAces (c : cs) = countCard c + countAces cs
where countCard Joker = 1
countCard (NormalCard Ace _) = 1
countCard _ = 0
然后它起作用了! 所以我的问题是,为什么第一个版本不起作用?
这是错误:
* 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'))
非常感谢花时间阅读这篇文章的人。
答案 0 :(得分:7)
在您的后卫c == (NormalCard Ace _)
中,您没有执行模式匹配;您正在尝试将c
与尚未指定适合的新值Card
进行比较。 _
是一个“空洞”,它是一个非值,会触发错误,但会确定_
应该具有的 type 类型,这对于调试和开发很有用。
要进行模式匹配,请使用显式case
表达式:
countAces (c:cs) = case c of
(NormalCard Ace _) -> 1 + countAces cs
Joker -> 1 + countAces cs
otherwise -> countAces cs
由于case
是表达式,而不是语句,因此您可以对其进行重构以减少重复:
countAces (c:cs) = countAces cs + case c of
(NormalCar Ace _) -> 1
Joker -> 0
otherwise -> 0
基本上内联了您在第二次尝试中定义的countCard
函数。
答案 1 :(得分:5)
这个问题更多地是关于您的思维方式是什么意思,而不是关于Haskell本身(如果您询问Haskell本身,那么答案是:“因为这就是语言的工作方式”)。
因此,我将尽力吸引您的想象力:
在第一种情况下,您有一个表达式,其结果将为True
或False
-两者都是有效的结果。您正在使用现有功能(==)
执行比较。此函数采用两个值-且出于完全相同的原因,您需要完全提供它们而没有漏洞-为什么您不能写(2 + _) * 10
并期望它求值成数字。
在第二种情况下,您使用语言构造=
。此构造不是返回值的函数。它用于构建定义。编写a = 2
时,并不是在编写可以为true或false的表达式。您正在根据a
来定义2
。它可以工作并且永远为真-否则将无法编译。在这种情况下-您可以使用孔。当您写a _ = 2
时,您实际上说的是:无论您对a
申请什么,都会得到2
。
答案 2 :(得分:-1)
在不阅读手册的情况下,我的猜测是,使用“ ==”时,您正在比较值,这里是不同的结构。在第二种解决方案中,您将对结构使用模式匹配。
当您在Haskell中定义具有替代结构的抽象数据类型时,“派生Eq”不足以比较这些不同的结构,则必须为相等性提供一个定义。只要在两个Card元素上定义“ ==”,就可以在第一种情况下使用“ ==“。