实际上,它们本质上是完全不同的!至少在 Haskell 中,无论如何。
守卫既简单又灵活:它们本质上只是特殊语法,可以转换为一系列 if/then 表达式。你可以在守卫中放置任意的布尔表达式,但它们不会做任何你不能用常规做的事情if
。
模式匹配做了几件额外的事情:它们是解构数据的唯一方法,并且它们在其范围内绑定标识符。就像守卫等同于if
表达式一样,模式匹配等同于case
表达式。声明(无论是在顶层,还是在类似let
表达式的地方)也是一种模式匹配形式,“正常”定义与普通模式匹配,即单个标识符。
模式匹配也往往是 Haskell 中实际发生的主要方式——尝试解构模式中的数据是强制评估的少数事情之一。
顺便说一句,您实际上可以在顶级声明中进行模式匹配:
square = (^2)
(one:four:nine:_) = map square [1..]
这有时对一组相关定义很有用。
GHC 还提供了 ViewPatterns 扩展,它结合了两者;您可以在绑定上下文中使用任意函数,然后对结果进行模式匹配。当然,这仍然只是普通东西的语法糖。
至于日常问题用在哪里,这里有一些粗略的指南:
对于可以直接匹配一两个构造函数的任何内容,绝对使用模式匹配,在这种情况下,您并不真正关心整个复合数据,而是关心大部分结构。该@
语法允许您将整个结构绑定到一个变量,同时还对其进行模式匹配,但是在一个模式中做太多的事情会很快变得丑陋和不可读。
当您需要根据一些与模式不完全对应的属性做出选择时,一定要使用守卫,例如比较两个Int
值以查看哪个更大。
如果您只需要来自大型结构内部的几条数据,特别是如果您还需要将结构作为一个整体使用,那么守卫和访问器函数通常比一些充满@
and的怪异模式更具可读性_
。
如果您需要对由不同模式表示的值执行相同的操作,但使用方便的谓词对它们进行分类,则使用带有保护的单个通用模式通常更具可读性。请注意,如果一组守卫不是详尽无遗的,那么所有守卫失败的任何事情都将下降到下一个模式(如果有的话)。因此,您可以将通用模式与一些过滤器结合起来以捕获异常情况,然后对其他所有内容进行模式匹配以获得您关心的细节。
绝对不要将守卫用于可以用模式进行简单检查的事情。检查空列表是典型的例子,为此使用模式匹配。
一般来说,当有疑问时,只要坚持默认模式匹配,它通常会更好。如果一个模式开始变得非常丑陋或令人费解,那么请停下来考虑你还能如何编写它。除了使用守卫之外,其他选项包括将子表达式提取为单独case
的函数或将表达式放入函数体中,以便将一些模式匹配向下推送到它们上并从主定义中移出。