haskell 模式匹配与警卫

tcbh2hod  于 9个月前  发布在  其他
关注(0)|答案(1)|浏览(81)

我试图解决一个Haskell任务,编写一个涉及扑克牌自定义数据类型的函数。该函数应该接受一张牌作为参数,并返回该牌的值,该值基于其排名(Ace -> 11,Jack/Queen/King -> 10,Numbers 2-9 -> 2-9)。这个问题基本上是需要访问数据构造函数的参数,以防卡片等级具有数值。
我找到了一个使用模式匹配的解决方案,感谢thisthis thread,我还找到了如何使用case of和模式保护(cardValueGuards)实现它。
我仍然想知道是什么让模式匹配在技术层面上工作。我想知道在这个例子中,一种方法是否比另一种方法更好。(见下面的问题。)

data Color = Red | Black deriving (Show, Eq)
data Suit = Clubs | Diamonds | Hearts | Spades deriving (Show, Eq)
data Rank = Num Int | Jack | Queen | King | Ace deriving (Show, Eq)
data Card = Card { suit :: Suit, rank :: Rank } deriving (Show, Eq)

-- Working solutions
cardValue :: Card -> Int
cardValue card = selectNum (rank card)
    where
        selectNum :: Rank -> Int
        selectNum Ace = 11
        selectNum (Num a) = a
        selectNum _ = 10

cardValueCase :: Card -> Int
cardValueCase card = case rank card of
    Ace -> 11
    (Num a) -> a
    otherwise -> 10

cardValueGuards :: Card -> Int
cardValueGuards card
    | rank card == Ace = 11
    | (Num a) <- rank card  = a
    | otherwise = 10

cardValueGuardsLong :: Card -> Int
cardValueGuardsLong card
    | rank card == Ace = 11
    | rank card == (Num 2) = 2
    | rank card == (Num 3) = 3
    | rank card == (Num 4) = 4
    | rank card == (Num 5) = 5
    | rank card == (Num 6) = 6
    | rank card == (Num 7) = 7
    | rank card == (Num 8) = 8
    | rank card == (Num 9) = 9
    | otherwise = 10

下面是我以前的尝试。(我假设rank card == (Num a) = a失败是因为使用函数(==)需要特定的值而不是类型变量。

-- Not working
cardValueGuardsOld :: Card -> Int
cardValueGuardsOld card
    | rank card == Ace = 11
    | rank card == (Num a) = a
    | otherwise = 10

详细说明:根据我的理解,当使用数据构造函数来构造Records的值时,Haskell创建了一个函数,该函数返回与记录名称相关联的值。因此,使用mycard = Card Spades Ace创建一张卡片可以让您通过使用rank mycard(返回Ace)访问卡片的等级。对于mycard2 = Card Diamonds (Num 8),它将返回Num 8
从第二个线程关于模式匹配和守卫之间的区别来看,守卫基本上是if-表达式,而模式匹配就像case of。在this answer中,它说不同之处在于模式匹配只检查“是否使用给定的构造函数创建了一个值”,而模式匹配绑定了变量。
直觉上,我会理解selectNum (rank card)后面跟着selectNum (Num a) = a,基本上和| rank card == (Num a) = a做同样的事情。在cardValuecardValueGuardsOld中,我都得到了卡片的等级,然后将其传递给检查其值。显然有些不同,但从我目前所读到的内容来看,我仍然不明白为什么在| rank card == (Num a) = a失败时允许模式匹配版本。

  • 模式匹配和case of到底是如何实现的(在编译器级别上)?在cardValueGuardsOld中,当相等性检查失败时,允许它们工作的区别是什么?它与作用域有关吗?还是因为模式匹配只检查构造函数或其他东西?
  • 在这种情况下,标准模式匹配/case of比使用guards更可取还是更不可取?如果我真的想检查数字卡只有2到9的数字,这会让警卫成为更好的选择吗?
5tmbdcev

5tmbdcev1#

(我假设rank card == (Num a) = a失败是因为使用函数(==)需要特定的值而不是类型变量。
是的。(这里a是一个普通变量,而不是类型变量。)
我仍然不明白为什么模式匹配版本是允许的,而| rank card == (Num a) = a失败。
正如你所说:因为(==)不是特殊的语法,它是一个像许多其他函数一样的函数。编写x == y与编写函数调用(==) x y相同。与守卫比较

...
| myComparisonFunction (rank card) (Num a) = a

此函数调用需要两个 * 表达式 * rank cardNum a作为参数。第二个表达式涉及到一个变量a,这个变量之前没有定义,所以编译器会引发一个错误。经验法则是:表达式中变量的值需要是已知的,以便计算表达式的值。(这在递归定义中部分是正确的,但让我们忽略它。
相比之下,patterns 定义了它们内部变量的值。如果我们将一个值与一个模式匹配,这可能失败(构造函数不匹配)或成功,在后一种情况下,模式中的所有变量都绑定到一个值,定义它们。
因此,虽然语法Num a既可以用作表达式,也可以用作模式,但其语义在这两种情况下有很大的不同。
模式匹配和case是如何实现的(在编译器级别)?当cardValueGuardsOld中的相等性检查失败时,允许它们工作的区别是什么?它与作用域有关吗?还是因为模式匹配只检查构造函数或其他东西?
函数调用与模式匹配、表达式与模式之间的区别就在于此。
当你提到模式只能涉及构造函数,而不能涉及通用函数时,你也是正确的。我们不能写

f (g x y) = ...

因为g不是构造函数。我们可以写

f (G x y) = ...

构造函数G
同样,对于大多数类型,函数(==)无论如何都是使用模式匹配实现的。因此,直接使用模式而不是调用==是一个好主意。
还有一些Haskell的高级特性(如GADT)只能使用模式。
最后,使用模式避免了一个常见的设计陷阱,称为“布尔盲”,其中布尔值被广泛使用,而不是更丰富的类型(例如,Maybe a, Either a b)。过多地关注布尔值通常会导致糟糕的Haskell代码。
在这种情况下,标准的模式匹配/情况是比使用保护更可取还是更不可取,或者这是一个品味问题?
虽然在某些情况下,可能有一种方法可以同时使用这两种方法来解决任务,但模式匹配应该是“默认”方法。在模式匹配工作时使用==不是一个好主意。
考虑

data T = A | B deriving Eq

f :: T -> String
f A = "a"
f B = "b"

g :: T -> String
g x
  | x == A = "a"
  | x == B = "b"

使用-Wall打开警告后,GHC可以检测到f处理所有情况,并且不会生成任何警告。
这不是g的情况。GHC将警告说,如果没有最终的otherwise案例,所有的警卫都可能是假的。这很烦人
另外,如果我们稍后添加一个新的构造函数呢?

data T = A | B | C deriving Eq

现在我们在f上得到一个错误,这很好,因为我们需要为新的情况修复函数。相比之下,g保持编译,并将在运行时崩溃。
如果我真的想检查数字卡只有2到9的数字,这会让警卫成为更好的选择吗?
如果我们想检查10 <= x && x <= 1000,我们不能用990个模式来做。

相关问题