简单了解下模式匹配

tc39 有个草案:proposal-pattern-matching,该草案描述了来自函数式编程语言中的模式匹配语法。

什么是模式匹配

模式匹配是基于数据的结构来选择不同行为的手段之一,其方式类似于解构。比如,你可以毫不费力地用指定的属性来匹配对象并且将这些属性的值绑定到匹配分支上。模式匹配使非常简洁和高度可读的函数式模式成为可能,并且已存在于许多语言之中,如 RustF#

一句话解释

模式匹配类似 switch case ,而 switch case 限定相等比较,而模式匹配是类型结构符合,能支持更复杂的比对。

Rust 的模式匹配语法

1
2
3
4
5
6
7
8
9
match target {
模式1 => 表达式1,
模式2 => {
语句1;
语句2;
表达式2
},
_ => 表达式3
}

草案描述

因为草案迟迟处于 Stage 1,因此许多尚在讨论中,目前的语法仍处于雏形。

目前给出的 示范代码为如下:

1
2
3
4
5
6
7
8
9
10
match (res) {
when ({ status: 200, body, ...rest }): handleData(body, rest)
when ({ status, destination: url }) if (300 <= status && status < 400):
handleRedirect(url)
when ({ status: 500 }) if (!this.hasRetried): do {
retry(req);
this.hasRetried = true;
}
default: throwSomething();
}

除了其中的 if 语句,其余和 switch case 语法很相似。

TS社区的实现

ts-pattern

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { match, __, not, select, when } from "ts-pattern";

const reducer = (state: State, event: Event): State =>
match<[State, Event], State>([state, event])
.with([{ status: "loading" }, { type: "success" }], ([, event]) => ({
status: "success",

data: event.data,
}))

.with(
[{ status: "loading" }, { type: "error", error: select() }],

(error) => ({
status: "error",

error,
})
)

.with([{ status: not("loading") }, { type: "fetch" }], () => ({
status: "loading",

startTime: Date.now(),
}))

.with(
[
{ status: "loading", startTime: when((t) => t + 2000 < Date.now()) },

{ type: "cancel" },
],

() => ({
status: "idle",
})
)

.with(__, () => state)

.exhaustive();

ts-parttern 与草案的区别在于,with 来指代 草案中的 when ,并且需要使用 exhaustive 函数来执行。

思考:为什么要有模式匹配这样的语法

我个人的理解是 switch case 语法无法对一个复杂对象中的多个属性进行同时判断,而用if else 又会损失可读性,比如如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const fn = (obj) => {
if (obj.a === 1 && obj.b == 'hello') {
return 'x'
} else if (obj.a === 1 && obj.c === null) {
return 'xx'
} else if (obj.d === 'd') {
if (obj.e === 'e') {
return 'e'
} else {
return 'not e'
}
} else {
return 'no match'
}
}

如果使用模式匹配的话,可以简化成以下,可读性增强了不少,尤其对象更复杂嵌套的场景,可读性提升会更明显吧。

1
2
3
4
5
6
7
const fn = (obj) => match (obj) {
when ({a: 1, b: 'hello'}) : 'x';
when ({a: 1, c: null}): 'xx';
when ({d: 'd', e: 'e'}): 'e';
when ({d: 'd'}): 'not e';
default: 'not match'
}