为什么我不能这样编写函数?

时间:2018-12-23 13:55:36

标签: haskell functional-programming

我正在为“函数式编程简介”考试学习。 这是我遇到的问题之一:

“以下数据类型用于表示一副牌:

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'))

非常感谢花时间阅读这篇文章的人。

3 个答案:

答案 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本身,那么答案是:“因为这就是语言的工作方式”)。

因此,我将尽力吸引您的想象力:

在第一种情况下,您有一个表达式,其结果将为TrueFalse-两者都是有效的结果。您正在使用现有功能(==)执行比较。此函数采用两个值-且出于完全相同的原因,您需要完全提供它们而没有漏洞-为什么您不能写(2 + _) * 10并期望它求值成数字。

在第二种情况下,您使用语言构造=。此构造不是返回值的函数。它用于构建定义。编写a = 2时,并不是在编写可以为true或false的表达式。您正在根据a来定义2。它可以工作并且永远为真-否则将无法编译。在这种情况下-您可以使用孔。当您写a _ = 2时,您实际上说的是:无论您对a申请什么,都会得到2

答案 2 :(得分:-1)

在不阅读手册的情况下,我的猜测是,使用“ ==”时,您正在比较值,这里是不同的结构。在第二种解决方案中,您将对结构使用模式匹配。

当您在Haskell中定义具有替代结构的抽象数据类型时,“派生Eq”不足以比较这些不同的结构,则必须为相等性提供一个定义。只要在两个Card元素上定义“ ==”,就可以在第一种情况下使用“ ==“。