模式匹配

在计算机科学中，模式匹配是检查给定记号序列中，是否存在某种模式的组成部分的行为。与模式识别相反，匹配通常必须是精确的。模式通常具有序列或树状结构的形式。模式匹配的用途包括：输出一个模式在一个记号序列中的位置（如果有的话），输出匹配模式的一些组成部分，以及用一些其他的记号序列替换匹配模式（即搜索和替换）。

## 概述

在一些编程语言中，模式被用作一种通用工具，基于数据的结构来处理数据，包括C#、 F#、Haskell、ML、Python、Ruby、Rust、Scala、Swift、仓颉和Mathematica的符号式Wolfram语言，它们拥有表达树状结构的特殊语法，和基于它的条件执行和值检索的语言构造。

编程之时经常可以给出逐个尝试的交替 (形式语言理论)式模式，它产生强力的条件编程构造。模式匹配有时包括对守卫子句的支持。

字符序列也就是字符串，经常使用正则表达式来描述，并使用像回溯这样的技术来进行匹配。解析算法经常依赖模式匹配来将字符串变换成语法树。

## 历史

具有模式匹配构造的早期编程语言包括：COMIT（1957年）、SNOBOL（1962年）、具有树状模式的Refal（1968年）、Prolog（1972年）、SASL（1976年）、NPL（1977年）和KRC（1981年）。

很多文本编辑器支持各种的模式匹配：支持正则表达式查找的QED (文本编辑器)，和在查找中支持`OR`运算的某些版本的TECO (text editor)。计算机代数系统一般支持在代数表达式上的模式匹配。

## 简单模式

在模式匹配中，最简单的模式是一个明确的值或一个变量。例如，考虑采用Haskell语法的一个简单函数，这里函数参数不放在圆括号内但用空白分隔，`=`不是赋值而是定义：

```haskell
f 0 = 1
```

这里的`0`是一个单一值模式。只要`f`被给予`0`作为参数，这个模式就匹配，并且函数返回`1`。对于任何其他参数，匹配失败，因而这个函数也失败。因为在函数定义中，语法上支持交替式模式，可以继续将定义扩展为接受更一般性的参数：

```haskell
f n = n * f (n-1)
```

这里的第一个`n`是单一变量模式，它绝对的匹配任何参数，并将它绑定到名字`n`，而用在定义的余下部分之中。在Haskell中（至少不似Hope），模式被依次尝试，所以第一个定义仍适用于输入是`0`的非常特殊情况，而对于任何其他参数，函数返回`n * f (n-1)`，具有`n`是为参数。

通配符模式（通常写为`_`）也是简单的：就像一个变量名字，它匹配任何值，但是不把这个值绑定到任何名字。在简单字符串匹配情况下的匹配通配符算法，已经发展出很多递归和非递归的变体。

在Haskell中，下面的代码定义了一个代数数据类型`Color`，它有一个单一的数据构造子`ColorConstructor`，包装一个整数和一个字符串：

```haskell
data Color = ColorConstructor Integer String
```

例如，要得到`Color`类型的整数部分的一个函数可以写为：

```haskell
integerPart (ColorConstructor theInteger _) = theInteger
```

要得到字符串部分：

```haskell
stringPart (ColorConstructor _ theString) = theString
```

这些函数可以通过Haskell的数据记录语法自动创建。

模式匹配还可应用来过滤特定结构的数据。例如，在Haskell中可以使用列表推导式进行这种过滤：

```haskell
data ABint = A Int | B Int
[A x|A x <- [A 1, B 1, A 2, B 2]]
```

求值结果为：

```haskell
[A 1, A 2]
```

## 序列模式

从上述原始模式，可以建造更加复杂的模式，通常采用的方式，相同于通过组合其他值来建造值。区别在于，具有变量和通配符部分，模式不建造成一个单一值，而是匹配一组值，它们是具体元素和允许在模式结构内变化的元素的组合。

在Haskell和一般的函数式编程语言中，列表是主要数据结构，它通常被定义为一种典型的代数数据类型：

```haskell
data List a = Nil | Cons a (List a)
```

`Cons`是构造（construct）的简写。很多语言针对以这种方式定义的列表提供特殊语法。例如，Haskell和ML，分别将`[]`用于`Nil`，`:`或`::`用于`Cons`，并将方括号用于整个列表。所以Cons 1 (Cons 2 (Cons 3 Nil))通常在Haskell中写为1:2:3:或1,2,3，在OCaml中写为1::2::3::或1;2;3。

列表被定义为空列表，或一个元素构造在一个现存的列表上。在Haskell语法下写为：

```haskell
[] -- 空列表
x:xs -- 元素x构造在列表xs之上
```

具有一些元素的一个列表的结构，就是`element:list`。在模式匹配的时候，断定特定部分的数据等于特定模式。例如，在如下函数中：

```haskell
head (element:list) = element
```

这里断定了`head`的实际参数的第一个元素被称为`element`，并且这个函数返回这个元素。之所以知道它是第一个元素的，是因为列表的定义方式，它是一个单一元素构造在一个列表之上，这个单一元素必定是第一个元素。空列表根本不匹配这个模式，因为空列表没有头部（要构造的第一个元素）。

在这个例子中，我们没有用到`list`，可以漠视它，而将函数写为：

```haskell
head (element:_) = element
```

## 树状模式

树状模式一般用来匹配由递归数据类型生成的复杂的树状结构，比如编程语言抽象语法树。它通过开始于一个节点，指定某些分支以及节点，并且通过变量或通配符留下一些不指定，来描述一个树的一部分。

下面是在OCaml下，定义一个红黑树和在元素插入后再平衡的函数的例子：

```ocaml
type color = Red | Black
type 'a tree = Empty | Tree of color * 'a tree * 'a * 'a tree

let rebalance t = match t with
    | Tree (Black, Tree (Red, Tree (Red, a, x, b), y, c), z, d)
    | Tree (Black, Tree (Red, a, x, Tree (Red, b, y, c)), z, d)                                  
    | Tree (Black, a, x, Tree (Red, Tree (Red, b, y, c), z, d))
    | Tree (Black, a, x, Tree (Red, b, y, Tree (Red, c, z, d)))
        ->  Tree (Red, Tree (Black, a, x, b), y, Tree (Black, c, z, d))
    | _ -> t (* the 'catch-all' case if no previous pattern matches *)
```

## 字符串模式

迄今最常用形式的模式匹配涉及字符串。在很多编程语言中，使用特定的字符串语法来表示正则表达式，它是描述字符串的模式。

在Haskell中，如下模式匹配有两个字符并且开始于`'a'`的字符串:

```haskell
['a', _]
```

可以介入符号式实体，来表示一个字符串的很多不同类的有关特征。在Haskell中，可以使用守卫来进行匹配：

```haskell
[letter, digit] | isAlpha letter && isDigit digit
```

它将匹配首个字符是一个字母，接着的是一个数字的字符串。

符号式字符串操纵的主要好处，是它可以与编程语言的其余部分完全的集成，而非成为一个独立的、专用的子单元。这个语言的整体能力，可以放大到建造模式自身，或分析和变换包含它们的程序。

## Python的模式匹配

### 定义

在Python版本3.10中，模式匹配的语法应该是：

```python3
match subject:
    case <pattern_1>:
        <action_1>
    case <pattern_2>:
        <action_2>
    case <pattern_3>:
        <action_3>
    case _:
        <action_wildcard>
```

其中 `subject`表示原始数据，`<pattern_*>`表示不同模式，`_`表示通配符，如果在上文中没有找到精确匹配的对象，将会使用通配符。如果一个模式匹配没有精确匹配上且没有通配符，整个语句不做任何作用（no-op）。

### 例子

在Python版本3.10中：

```python3
def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:         #省略此行及下一行以创造一个没有通配符的模式匹配
            return "Something's wrong with the Internet"
```

如果省略最后两行，当`status`为500时什么都不会发生。

同理，模式匹配也可用于`class`中：

```python3
class Point:
    x: int
    y: int

def location(point):
    match point:
        case Point(x=0, y=0):
            print("Origin is the point's location.")
        case Point(x=0, y=y):
            print(f"Y={y} and the point is on the y-axis.")
        case Point(x=x, y=0):
            print(f"X={x} and the point is on the x-axis.")
        case Point():
            print("The point is located somewhere else on the plane.")
        case _:
            print("Not a point")
```

和其他语言中的模式匹配一样，Python中也可以在匹配的语句中使用通配符：

```python3
match test_variable:
    case ('warning', code, 40):
        print("A warning has been received.")
    case ('error', code, _):
        print(f"An error {code} occurred.")
```

## 外部链接

- Nikolaas N. Oosterhof, Philip K. F. Hölzenspies, and Jan Kuper. Application patterns. A presentation at Trends in Functional Programming, 2005
- JMatch : the Java programming language extended with pattern matching
- ShowTrend: Online pattern matching for stock prices
- An incomplete history of the QED Text Editor by Dennis Ritchie - provides the history of regular expressions in computer programs
- The Implementation of Functional Programming Languages, pages 53–103 Simon Peyton Jones, published by Prentice Hall, 1987.
- Nemerle, pattern matching .
- Erlang, pattern matching .
- Prop: a C++ based pattern matching language, 1999
- PatMat: a C++ pattern matching library based on SNOBOL/SPITBOL
- Temur Kutsia. Flat Matching. Journal of Symbolic Computation 43(12): 858–873. Describes in details flat matching in Mathematica.
- EasyPattern language pattern matching language for non-programmers