我对Haskell和一般的函数式编程都很陌生。我的问题非常基本。 Pattern Matching和Guards之间有什么区别?
使用模式匹配的功能
check :: [a] -> String
check [] = "Empty"
check (x:xs) = "Contains Elements"
使用警卫的功能
check_ :: [a] -> String
check_ lst
| length lst < 1 = "Empty"
| otherwise = "Contains elements"
对我而言,模式匹配和防护看起来基本相同。两者都评估条件,如果为true则将执行挂钩的表达式。我的理解是否正确?
在这个例子中,我可以使用模式匹配或防护来获得相同的结果。但是有些东西告诉我,我错过了一些重要的事情。我们可以一直替换另一个吗?
有人可举例说明模式匹配优于警卫,反之亦然吗?
答案 0 :(得分:57)
实际上,它们基本上是完全不同的!至少在Haskell中,无论如何。
Guards既简单又灵活:它们本质上只是特殊语法,可转换为一系列if / then表达式。你可以在守卫中放置任意布尔表达式,但是他们不能做任何你不能用常规if
做的事情。
模式匹配会做一些额外的事情:它们是解构数据的唯一方法,并且绑定其范围内的标识符。与保护等同于if
表达式的意义相同,模式匹配等同于case
表达式。声明(在顶层或类似let
表达式)也是一种模式匹配形式,“普通”定义与普通模式匹配,即单个标识符。
模式匹配也往往是Haskell中实际发生的主要方式 - 试图解析模式中的数据是迫使评估的少数事情之一。
顺便说一句,您实际上可以在顶级声明中进行模式匹配:
square = (^2)
(one:four:nine:_) = map square [1..]
这有时对一组相关定义很有用。
GHC也provides the ViewPatterns extension这两种结合起来;您可以在绑定上下文中使用任意函数,然后在结果上进行模式匹配。当然,这仍然只是通常情况下的语法糖。
至于在哪里使用的日常问题,这里有一些粗略的指南:
对于任何可以直接匹配一个或两个构造函数的东西,绝对使用模式匹配,在那里你并不真正关心整个复合数据,而是关心大部分结构。 @
语法允许您将整体结构绑定到变量,同时还对其进行模式匹配,但在一种模式中执行太多操作可能会很快变得难看且无法读取。
当您需要根据某些与图案不完整对应的属性做出选择时,绝对使用防护,例如:比较两个Int
值,看哪哪个更大。
如果您只需要来自大型结构内部的几个数据,特别是如果您还需要将结构作为一个整体使用,那么守卫和访问器函数通常比一些充满{的怪异模式更具可读性。 {1}}和@
。
如果您需要对由不同模式表示的值执行相同的操作,但使用方便的谓词对它们进行分类,则使用带保护的单个通用模式通常更具可读性。请注意,如果一组防护措施不是详尽无遗的,那么所有防护措施失败的东西都将下拉到下一个模式(如果有的话)。因此,您可以将常规模式与某些过滤器结合使用以捕获异常情况,然后对其他所有内容进行模式匹配以获取您关注的详细信息。
绝对不要使用防护措施来处理那些可以用模式轻易检查的东西。检查空列表是典型的例子,使用模式匹配。
一般来说,如果有疑问,默认情况下坚持使用模式匹配,通常会更好。如果一个模式开始变得非常丑陋或令人费解,那么请停下来考虑如何编写它。除了使用警卫之外,其他选项包括将子表达式作为单独的函数提取或将_
表达式放在函数体内,以便将一些模式匹配向下推到它们之外并超出主要定义。
答案 1 :(得分:10)
对我而言,模式匹配和防护看起来基本相同。两者都评估条件,如果为true则将执行挂钩的表达式。我的理解是否正确?
不完全。第一种模式匹配不能评估任意条件。它只能检查是否使用给定的构造函数创建了值。
第二个模式匹配可以绑定变量。因此虽然模式[]
可能等同于保护null lst
(不使用长度,因为它不等同 - 稍后会更多),模式x:xs
肯定不等同保护not (null lst)
,因为该模式绑定了变量x
和xs
,而后卫则没有。
关于使用length
的说明:使用length
来检查列表是否为空是非常糟糕的做法,因为要计算完整列表所需的长度,这将需要{ {1}}时间,只需检查列表是否为空,O(n)
时间O(1)
或模式匹配。进一步使用`length'只是简单无法在无限列表上工作。
答案 2 :(得分:9)
首先,你可以将布尔表达式放在一个守卫中。
与列表推导一样,布尔表达式可以在模式保护中自由混合。例如:
f x | [y] <- x , y > 3 , Just z <- h y = ...
<小时/> 更新
Learn You a Haskell有一个关于差异的引用:
虽然模式是确保某个值符合某种形式并对其进行解构的一种方式,但是保护是一种测试某个值(或其中几个)的属性是真还是假的方法。这听起来很像if语句,而且非常相似。事情是,当你有几个条件并且它们与模式非常匹配时,守卫会更具可读性。
答案 3 :(得分:5)
除了其他好的答案之外,我将尝试具体讲述警卫:警卫只是语法糖。如果您考虑一下,您的程序通常会有以下结构:
f y = ...
f x =
if p(x) then A else B
也就是说,如果模式匹配,则紧跟在if-then-else之后。一名警卫直接将这种歧视折叠成模式匹配:
f y = ...
f x | p(x) = A
| otherwise = B
(otherwise
在标准库中定义为True
。它比if-then-else链更方便,有时它也使代码变得更简单,因此它比if-then-else结构更容易编写。
换句话说,它是另一种结构上的糖,在很多情况下可以大大简化你的代码。你会发现它消除了许多if-then-else链并使你的代码更具可读性。