模式匹配

在電腦科學中，模式匹配是檢查給定記號序列中，是否存在某種模式的組成部分的行為。與模式識別相反，匹配通常必須是精確的。模式通常具有序列或樹狀結構的形式。模式匹配的用途包括：輸出一個模式在一個記號序列中的位置（如果有的話），輸出匹配模式的一些組成部份，以及用一些其他的記號序列替換匹配模式（即搜索和替換）。

## 概述

在一些程式語言中，模式被用作一種通用工具，基於數據的結構來處理數據，包括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