为什么在 OCaml 中使用模式匹配
Why use pattern matching in OCaml
让我们考虑一下这个小函数
let f x =
match x with
0 -> 1 |
_ -> x ;;
这在逻辑上等同于
let f x =
if x = 0 then 1 else x ;;
如果我们可以使用if/else实现相同的模式匹配,那么模式匹配的目的是什么?
检测到部分模式匹配:
type number = Zero | One | Two;
let f= function
Zero -> 0
| One -> 1 ;;
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
Two
val f : number -> int = <fun>
在您的精确示例中,模式匹配不会带来很多,因为您只有 2 个案例,更重要的是因为您的模式没有任何变量。用if/then/else写这个例子你就明白了:
let rec map f = function
[] -> []
| a::l -> let r = f a in r :: map f l
另请注意,如果您有多余的案例或您忘记了某些案例,模式匹配会警告您。
通常模式匹配允许编译器应用更积极的优化技术。在 if/then/else 表达式中,条件是一个任意表达式,可以包含副作用。例如,相等运算符可以做任何事情,因此编译器通常不能依赖 x=0
表示 x 等于 0。在模式匹配子句中总是常量,匹配意味着句法相等,不能重载,所以很容易直接编译成汇编比较操作。在带有 if
的示例中,比较通常会编译为函数调用(但 afaik 在这种情况下编译器足够聪明,生成的代码将是相同的)。
但是if/then/else和模式匹配的主要区别是后者是运行并行编译成二叉搜索树嵌入程序集,而if/then/else只是一个线性比较序列(有关详细信息,请参阅 )。
更新
为了满足 OP 的好奇心,我添加了一些汇编输出。不需要懂x86汇编,比较多条指令,基本思路就可以了。你会看到的。
正如我预测的那样,确实,编译器发出了几乎相同的代码,对你来说具有相同的性能示例:
函数with_match
已编译成高效代码(注意OCaml中的0
是1
)
with_match:
.L101:
cmpq , %rax
je .L100
ret
.L100:
movq , %rax
ret
对于函数 with_if
编译器也给出了最优代码。唯一不同的是,在with_if
函数中,跳转指令中的条件被取反了。
with_if:
.L103:
cmpq , %rax
jne .L102
movq , %rax
ret
.L102:
ret
这是可能的,因为编译器使用了一个技巧,允许他处理
=
作为一个特殊函数,附有一些理论。但通常这是不可能的,因为 =
可以是任意函数。我们很容易混淆编译器,方法是在文件开头添加以下行:
let (=) x y = x = y
现在所有技巧都被禁用,编译器发出这段低效代码。
with_if:
subq , %rsp
.L105:
movq %rax, 0(%rsp)
movq , %rsi
movq %rax, %rdi
movq _caml_equal, %rax
call _caml_c_call
.L106:
movq _caml_young_ptr, %r11
movq (%r11), %r15
cmpq , %rax
je .L104
movq , %rax
addq , %rsp
ret
.L104:
movq 0(%rsp), %rax
addq , %rsp
ret
综上所述,我想强调的是,人们不应该更喜欢 match 而不是 if 或 vice verse。应该选择更干净的结构并产生更易读的代码。 ocaml 编译器相当不错,会为您生成高效的代码。
我个人更倾向于比赛,因为这反映了我的思维方式。我很难根据 if/then/else 结构进行推理,每当我阅读它们时,我都会在心里将它们翻译成 match with 从句。但这是我个人的问题。随意使用更适合您的结构。
让我们考虑一下这个小函数
let f x =
match x with
0 -> 1 |
_ -> x ;;
这在逻辑上等同于
let f x =
if x = 0 then 1 else x ;;
如果我们可以使用if/else实现相同的模式匹配,那么模式匹配的目的是什么?
检测到部分模式匹配:
type number = Zero | One | Two;
let f= function
Zero -> 0
| One -> 1 ;;
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
Two
val f : number -> int = <fun>
在您的精确示例中,模式匹配不会带来很多,因为您只有 2 个案例,更重要的是因为您的模式没有任何变量。用if/then/else写这个例子你就明白了:
let rec map f = function
[] -> []
| a::l -> let r = f a in r :: map f l
另请注意,如果您有多余的案例或您忘记了某些案例,模式匹配会警告您。
通常模式匹配允许编译器应用更积极的优化技术。在 if/then/else 表达式中,条件是一个任意表达式,可以包含副作用。例如,相等运算符可以做任何事情,因此编译器通常不能依赖 x=0
表示 x 等于 0。在模式匹配子句中总是常量,匹配意味着句法相等,不能重载,所以很容易直接编译成汇编比较操作。在带有 if
的示例中,比较通常会编译为函数调用(但 afaik 在这种情况下编译器足够聪明,生成的代码将是相同的)。
但是if/then/else和模式匹配的主要区别是后者是运行并行编译成二叉搜索树嵌入程序集,而if/then/else只是一个线性比较序列(有关详细信息,请参阅
更新
为了满足 OP 的好奇心,我添加了一些汇编输出。不需要懂x86汇编,比较多条指令,基本思路就可以了。你会看到的。
正如我预测的那样,确实,编译器发出了几乎相同的代码,对你来说具有相同的性能示例:
函数with_match
已编译成高效代码(注意OCaml中的0
是1
)
with_match:
.L101:
cmpq , %rax
je .L100
ret
.L100:
movq , %rax
ret
对于函数 with_if
编译器也给出了最优代码。唯一不同的是,在with_if
函数中,跳转指令中的条件被取反了。
with_if:
.L103:
cmpq , %rax
jne .L102
movq , %rax
ret
.L102:
ret
这是可能的,因为编译器使用了一个技巧,允许他处理
=
作为一个特殊函数,附有一些理论。但通常这是不可能的,因为 =
可以是任意函数。我们很容易混淆编译器,方法是在文件开头添加以下行:
let (=) x y = x = y
现在所有技巧都被禁用,编译器发出这段低效代码。
with_if:
subq , %rsp
.L105:
movq %rax, 0(%rsp)
movq , %rsi
movq %rax, %rdi
movq _caml_equal, %rax
call _caml_c_call
.L106:
movq _caml_young_ptr, %r11
movq (%r11), %r15
cmpq , %rax
je .L104
movq , %rax
addq , %rsp
ret
.L104:
movq 0(%rsp), %rax
addq , %rsp
ret
综上所述,我想强调的是,人们不应该更喜欢 match 而不是 if 或 vice verse。应该选择更干净的结构并产生更易读的代码。 ocaml 编译器相当不错,会为您生成高效的代码。
我个人更倾向于比赛,因为这反映了我的思维方式。我很难根据 if/then/else 结构进行推理,每当我阅读它们时,我都会在心里将它们翻译成 match with 从句。但这是我个人的问题。随意使用更适合您的结构。