模式匹配和守卫有什么区别?
我对 Haskell 和一般的函数式编程非常陌生。我的问题很基本。模式匹配和守卫有什么区别?
使用模式匹配的函数
check :: [a] -> String
check [] = "Empty"
check (x:xs) = "Contains Elements"
使用防护的函数
check_ :: [a] -> String
check_ lst
| length lst < 1 = "Empty"
| otherwise = "Contains elements"
在我看来,模式匹配和防护基本上是相同的。两者都评估一个条件,如果为 true 将执行与其挂钩的表达式。我的理解正确吗?
在此示例中,我可以使用模式匹配或防护来获得相同的结果。但有件事告诉我,我在这里错过了一些重要的事情。我们总能用其中一个替换另一个吗?
有人可以举例说明模式匹配优于守卫,反之亦然吗?
I am very new to Haskell and to functional programming in general. My question is pretty basic. What is the difference between Pattern Matching and Guards?
Function using pattern matching
check :: [a] -> String
check [] = "Empty"
check (x:xs) = "Contains Elements"
Function using guards
check_ :: [a] -> String
check_ lst
| length lst < 1 = "Empty"
| otherwise = "Contains elements"
To me it looks like Pattern Matching and Guards are fundamentally the same. Both evaluate a condition, and if true will execute the expression hooked to it. Am I correct in my understanding?
In this example I can either use pattern matching or guards to arrive at the same result. But something tells me I am missing out on something important here. Can we always replace one with the other?
Could someone give examples where pattern matching is preferred over guards and vice versa?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
事实上,它们本质上是不同的!至少在 Haskell 中是这样。
守卫既简单又灵活:它们本质上只是转换为一系列 if/then 表达式的特殊语法。您可以在防护中放置任意布尔表达式,但它们不会执行常规
if
无法执行的任何操作。模式匹配还可以做一些额外的事情:它们是解构数据的唯一方法,并且它们在其范围内绑定标识符。在同样的意义上,保护相当于
if
表达式,模式匹配相当于case
表达式。声明(无论是在顶层,还是在let
表达式中)也是模式匹配的一种形式,“正常”定义与简单模式(单个标识符)匹配。模式匹配也往往是 Haskell 中实际发生的主要方式——尝试解构模式中的数据是强制评估的少数事情之一。
顺便说一句,您实际上可以在顶级声明中进行模式匹配:
这有时对于一组相关定义很有用。
GHC 还提供 ViewPatterns 扩展< /a> 哪种结合了两者;您可以在绑定上下文中使用任意函数,然后对结果进行模式匹配。当然,这仍然只是普通内容的语法糖。
至于在哪里使用哪个的日常问题,这里有一些粗略的指南:
对于任何可以直接匹配一两个构造函数深度的东西,绝对使用模式匹配,而你并不真正关心复合。数据作为一个整体,但确实关心大部分结构。
@
语法允许您将整体结构绑定到变量,同时还可以对其进行模式匹配,但是在一个模式中执行太多操作可能会很快变得丑陋且难以阅读。当您需要根据某些与模式不完全对应的属性做出选择时,一定要使用防护措施,例如比较两个
Int
值以查看哪个更大。如果您只需要大型结构内部深处的几条数据,特别是如果您还需要将结构作为一个整体使用,则防护和访问器函数通常比一些充满
@ 的可怕模式更具可读性
和_
。如果您需要对不同模式表示的值执行相同的操作,但使用方便的谓词对它们进行分类,则使用带有保护的单个通用模式通常更具可读性。请注意,如果一组防护措施不是详尽无遗的,则所有未能通过所有防护措施的任何内容都将下降到下一个模式(如果有)。因此,您可以将一般模式与某些过滤器结合起来以捕获异常情况,然后对其他所有内容进行模式匹配以获得您关心的详细信息。
绝对不要对那些可以用模式简单检查的东西使用防护。检查空列表是典型的示例,为此使用模式匹配。
一般来说,当有疑问时,只需坚持默认的模式匹配,通常会更好。如果一个模式开始变得非常丑陋或令人费解,那么就停下来考虑一下你还能如何编写它。除了使用保护之外,其他选项还包括将子表达式提取为单独的函数或将 case 表达式放入函数体内,以便将一些模式匹配向下推到它们上面并从主定义中移出。
Actually, they're fundamentally quite different! At least in Haskell, at any rate.
Guards are both simpler and more flexible: They're essentially just special syntax that translates to a series of if/then expressions. You can put arbitrary boolean expressions in the guards, but they don't do anything you couldn't do with a regular
if
.Pattern matches do several additional things: They're the only way to deconstruct data, and they bind identifiers within their scope. In the same sense that guards are equivalent to
if
expressions, pattern matching is equivalent tocase
expressions. Declarations (either at the top level, or in something like alet
expression) are also a form of pattern match, with "normal" definitions being matches with the trivial pattern, a single identifier.Pattern matches also tend to be the main way stuff actually happens in Haskell--attempting to deconstruct data in a pattern is one of the few things that forces evaluation.
By the way, you can actually do pattern matching in top-level declarations:
This is occasionally useful for a group of related definitions.
GHC also provides the ViewPatterns extension which sort of combines both; you can use arbitrary functions in a binding context and then pattern match on the result. This is still just syntactic sugar for the usual stuff, of course.
As for the day-to-day issue of which to use where, here's some rough guides:
Definitely use pattern matching for anything that can be matched directly one or two constructors deep, where you don't really care about the compound data as a whole, but do care about most of the structure. The
@
syntax lets you bind the overall structure to a variable while also pattern matching on it, but doing too much of that in one pattern can get ugly and unreadable quickly.Definitely use guards when you need to make a choice based on some property that doesn't correspond neatly to a pattern, e.g. comparing two
Int
values to see which is larger.If you need only a couple pieces of data from deep inside a large structure, particularly if you also need to use the structure as a whole, guards and accessor functions are usually more readable than some monstrous pattern full of
@
and_
.If you need to do the same thing for values represented by different patterns, but with a convenient predicate to classify them, using a single generic pattern with a guard is usually more readable. Note that if a set of guards is non-exhaustive, anything that fails all the guards will drop down to the next pattern (if any). So you can combine a general pattern with some filter to catch exceptional cases, then do pattern matching on everything else to get details you care about.
Definitely don't use guards for things that could be trivially checked with a pattern. Checking for empty lists is the classic example, use a pattern match for that.
In general, when in doubt, just stick with pattern matching by default, it's usually nicer. If a pattern starts getting really ugly or convoluted, then stop to consider how else you could write it. Besides using guards, other options include extracting subexpressions as separate functions or putting
case
expressions inside the function body in order to push some of the pattern matching down onto them and out of the main definition.其一,您可以将布尔表达式放入守卫中。
例如:
Update
Learn You a Haskell 中有一段很好的引述,讲述了其中的区别:
For one, you can put boolean expressions within a guard.
For example:
Update
There is a nice quote from Learn You a Haskell about the difference:
不完全是。首先模式匹配不能评估任意条件。它只能检查是否使用给定的构造函数创建了值。
第二个模式匹配可以绑定变量。因此,虽然模式
[]
可能相当于保护null lst
(不使用长度,因为这不等价 - 稍后会详细介绍),但模式>x:xs
肯定不等于守卫not (null lst)
因为该模式绑定了变量x
和xs
>,守卫没有。关于使用
length
的注意事项:使用length
检查列表是否为空是非常糟糕的做法,因为要计算长度需要遍历整个列表,这将花费O(n)
时间,而仅检查列表是否为空则需要O(1)
时间(使用null
或模式匹配)。进一步简单地使用“length”在无限列表上不起作用。Not quite. First pattern matching can not evaluate arbitrary conditions. It can only check whether a value was created using a given constructor.
Second pattern matching can bind variables. So while the pattern
[]
might be equivalent to the guardnull lst
(not using length because that'd not be equivalent - more on that later), the patternx:xs
most certainly is not equivalent to the guardnot (null lst)
because the pattern binds the variablesx
andxs
, which the guard does not.A note on using
length
: Usinglength
to check whether a list is empty is very bad practice, because, to calculate the length it needs to go through the whole list, which will takeO(n)
time, while just checking whether the list is empty takesO(1)
time withnull
or pattern matching. Further using `length´ just plain does not work on infinite lists.除了其他好的答案之外,我将尝试具体说明守卫:守卫只是语法糖。如果您考虑一下,您的程序中通常会具有以下结构:
也就是说,如果模式匹配,则紧随其后的是 if-then-else 判别。防护将这种区分直接折叠到模式匹配中:(
otherwise
在标准库中被定义为True
)。它比 if-then-else 链更方便,有时它还使代码在变体方面更加简单,因此比 if-then-else 结构更容易编写。换句话说,它是另一种结构之上的糖,在许多情况下可以极大地简化您的代码。你会发现它消除了很多 if-then-else 链并使你的代码更具可读性。
In addition to the other good answers, I'll try to be specific about guards: Guards are just syntactic sugar. If you think about it, you will often have the following structure in your programs:
That is, if a pattern matches, it is followed right after by a if-then-else discrimination. A guard folds this discrimination into the pattern match directly:
(
otherwise
is defined to beTrue
in the standard library). It is more convenient than an if-then-else chain and sometimes it also makes the code much simpler variant-wise so it is easier to write than the if-then-else construction.In other words, it is sugar on top of another construction in a way which greatly simplifies your code in many cases. You will find that it eliminates a lot of if-then-else chains and make your code more readable.