我们前面的丘奇数推导中,已经掌握了一定的lambda演算技巧。接下来,是时候正式了解一下lambda演算了。
lambda的多种记法
因为时代原因,一些资料采用的lambda记法跟我们文章所写有一定区别,此处列出常见的记法。方便大家在查阅不同来源的资料时不至于混淆。
单字母命名lambda
在一些风格较为古老的资料中,只支持单字母参数名,这样,就会省略空格和点, <math xmlns="http://www.w3.org/1998/Math/MathML"> λ \lambda </math>λ符号后第一个字母为参数名,其后为表达式内容:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> ( λ x λ y x y ) z (\lambda x\lambda yxy)z </math>(λxλyxy)z
以我们本系列的语法表示为:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> ( λ x . λ y . x y ) z (\lambda x.\lambda y.x\ y)\ z </math>(λx.λy.x y) z
支持多参数
一些资料中,同样仅支持单字母作为lambda的参数名,但保留以点分割,这样可以支持柯里化的多参数记法,例如:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> λ x y . ( x y ) \lambda xy.(x\ y) </math>λxy.(x y)
以我们本系列的语法表示为:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> λ x . λ y . ( x y ) \lambda x.\lambda y.(x\ y) </math>λx.λy.(x y)
二者完全等价,尽管书写形式上是多参数,lambda只支持柯里化的多参数。
符号替换记法
一些资料中,会用一些记法来表示lambda替换的过程。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> [ x : = y ] ( x z ) [x:=y](x\ z) </math>[x:=y](x z)
表示在后面的表达式中,把x替换为y。
另外一些资料中,也会用斜杠来表示:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> [ y / x ] ( x z ) [y/x](x\ z) </math>[y/x](x z)
转换与归约规则
接下来我们来正式地介绍一下lambda演算的规则。
- α转换:换元变换,直觉上,可以理解为当我们改变一个参数名时,只要用到此参数的部分都跟着改名,那么函数跟原来的函数等价。记为:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> M ↠ α N M\ {\twoheadrightarrow}{\alpha}\ N \\ </math>M ↠α N
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> λ x . x ↠ α λ y . y \lambda x.x\ {\twoheadrightarrow}{\alpha}\ \lambda y.y </math>λx.x ↠α λy.y
- β归约:代入归约,直觉上,可以理解为我们调用一个函数时,把所有形参代换为实参进行化简。记为:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> M ↠ β N M\ {\twoheadrightarrow}{\beta}\ N \\ </math>M ↠β N
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> ( λ x . x z ) y ↠ β ( y z ) (\lambda x.x\ z)\ y\ {\twoheadrightarrow}{\beta}\ (y\ z) </math>(λx.x z) y ↠β (y z)
- η归约:调用归约,直觉上,可以理解为当一个函数内仅有另一个函数的调用,并透传参数时,实际上它们是同一个函数。记为:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> M ↠ η N M\ {\twoheadrightarrow}{\eta}\ N \\ </math>M ↠η N
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> λ x . y x ↠ η y \lambda x.y\ x\ {\twoheadrightarrow}{\eta}\ y </math>λx.y x ↠η y
Church-Rosser定理
有时,一个lambda表达式存在多个不同的归约方式,
Church-Rosser定理的形式化定义:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> ∀ M , N 1 , N 2 ∈ Λ : 若有 M ↠ β N 1 且 M ↠ β N 2 则 ∃ X ∈ Λ : N 1 ↠ β X 且 N 2 ↠ β X \forall M, N_1, N_2 \in \Lambda: \text{若有}\ M\twoheadrightarrow_\beta N_1 \ \text{且}\ M\twoheadrightarrow_\beta N_2 \ \text{则}\ \exists X\in \Lambda: N_1\twoheadrightarrow_\beta X \ \text{且}\ N_2\twoheadrightarrow_\beta X </math>∀M,N1,N2∈Λ:若有 M↠βN1 且 M↠βN2 则 ∃X∈Λ:N1↠βX 且 N2↠βX
我们把它画成图:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> M → β N 1 ↓ β ↓ β N 2 → β X \begin{array}{ccc} M & \xrightarrow{\text{β}} & N_1 \\ \downarrow \scriptstyle{β} & & \downarrow \scriptstyle{β} \\ N_2 & \xrightarrow{\text{β}} & X\\ \end{array} </math>M↓βN2β β N1↓βX
我们对照图来解释,就是如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> N 1 N_1 </math>N1和 <math xmlns="http://www.w3.org/1998/Math/MathML"> N 2 N_2 </math>N2存在,那么一定能找到一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> X X </math>X。
直觉上,可以Church-Rosser定理理解为β归约的"殊途同归",即无论以何种顺序对lambda函数进行归约,最终可以得到一个一致的结果。
从直觉上来看,Church-Rosser定理的成立是显然的:对于一个"算式"来说,不论我们先计算它的哪个部分,最终都能得到同样的结果。
Lambda演算的Church-Rosser定理有几种不同的形式化证明方法,考虑到证明过程较为枯燥冗长,本篇就不在这里给出了,对数学有兴趣的的同学可以自行查阅相关资料。
Lambda演算的Church-Rosser定理还可以推广到 <math xmlns="http://www.w3.org/1998/Math/MathML"> η \eta </math>η归约,进而可以证明 <math xmlns="http://www.w3.org/1998/Math/MathML"> β η \beta\eta </math>βη归约混合运算也符合Church-Rosser定理。
Church-Rosser定理揭示了lambda演算的重要性质:
- 并行特性,具有多个归约方向lambda演算可以依据其结构进行并行运算,以lambda为基础理论的计算机语言也有此性质。
- 推导一致性,不论如何进行归约,lambda演算不会得到自相矛盾的结果。
从Church-Rosser定理,我们很容易想到lambda表达式可以通过β归约,达到无法β归约的形式,这个形式被称为β正规形式。由Church-Rosser定理可知,若β正规形式存在,它必定是唯一的。
β正规形式可以被视为lambda演算的最终计算结果。
那么,是否每一个lambda函数都存在β正规形式呢?
不动点
我们来研究一个特殊的函数。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Ω = ( λ x . x x ) ( λ x . x x ) Ω = (λx. x\ x)\ (λx. x\ x) </math>Ω=(λx.x x) (λx.x x)
当我们尝试对Ω做β归约时,可以发现,Ω的β归约是它自身。因此,Ω被称作β归约的不动点(fix point)。显然,不动点Ω不存在β正规形式。
当我们加入一次函数调用,Ω函数就变成了Y组合子,它能够用于实现函数递归。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Y = λ f . ( λ x . f ( x x ) ) ( λ x . f ( x x ) ) Y = λf.(λx.f\ (x\ x))\ (λx.f\ (x\ x)) </math>Y=λf.(λx.f (x x)) (λx.f (x x))
我们也可以稍作变通,少写一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> f f </math>f,把它写作: <math xmlns="http://www.w3.org/1998/Math/MathML"> Y = λ f . ( λ x . x x ) ( λ x . f ( x x ) ) Y = λf.(λx.x\ x)\ (λx.f\ (x\ x)) </math>Y=λf.(λx.x x) (λx.f (x x)),。
这个形式与我们在除法一节推导出的Y组合子一致。经过一次β归约,就可以得到上面的形式。
具体到编程语言层面,考虑到Y组合子可能导致死循环,我们可以对 <math xmlns="http://www.w3.org/1998/Math/MathML"> f f </math>f的参数部分做逆η归约 ,形成 <math xmlns="http://www.w3.org/1998/Math/MathML"> Z Z </math>Z组合子。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Z = λ f . ( λ x . f ( λ z . x x z ) ) ( λ x . f ( λ z . x x z ) ) Z = λf.(λx.f\ (λz.x\ x\ z))\ (λx.f\ (λz.x\ x\ z)) </math>Z=λf.(λx.f (λz.x x z)) (λx.f (λz.x x z))
实际上,Y组合子的变体非常丰富,例如Haskell Curry本人发现的 <math xmlns="http://www.w3.org/1998/Math/MathML"> Θ Θ </math>Θ组合子。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Θ = λ f . ( λ x . λ y . f ( y x ) ) ( λ x . λ y . f ( y x ) ) Θ = λf.(λx.λy.f\ (y\ x))\ (λx.λy.f\ (y\ x)) </math>Θ=λf.(λx.λy.f (y x)) (λx.λy.f (y x))
尽管这些含有不动点的组合子破坏了β正规形式,但它们又是表达复杂计算逻辑的必备。
这也从侧面说明,我们无法通过机械进行β归约,去解决复杂的计算问题。
判定相等
既然β正规形式这条路走不通,那么是否有一种通用的方案,能够判定lambda表达式相等呢?
在一些情况下,这个问题非常简单,比如,如果两个lambda函数包含的符号完全一致,那它们两个必定等价,能够用三种规则互相转换的lambda表达式页总是互相等价的。
但对于含有不动点的lambda函数,可就没那么简单了。
此处我们不做严格证明,从感性的角度来思考一下这个问题的难度。
我们前面教程已经构造了分支逻辑和丘奇数基本运算,并且通过Y组合子可以实现递归,此处我们就使用大家更熟悉的符号系统来构造一个函数:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> f = λ x . { 1 if x = = 1 f ( x ∗ 3 + 1 ) if x % 2 ! = 0 , f ( x / 2 ) if x % 2 = = 0. f = \lambda x. \left\{ \begin{array}{lll} 1 & \text{if } x == 1 \\ f(x * 3 + 1) & \text{if } x\%2\ !=\ 0, \\ f(x / 2) & \text{if } x\%2 == 0. \\ \end{array} \right. </math>f=λx.⎩ ⎨ ⎧1f(x∗3+1)f(x/2)if x==1if x%2 != 0,if x%2==0.
我们来判断它是否与 <math xmlns="http://www.w3.org/1998/Math/MathML"> λ x . 1 \lambda x.1 </math>λx.1 等价。所以问题就转化成:对所有自然数x,如果它是奇数,就把它乘以三加一,如果它是偶数,则把它除以2,一直这样做下去,是否每个自然数都能变成1?
这个问题实质上等价于一个著名的未能解决的数学猜想:角谷静夫猜想(Collatz猜想)。
只要你愿意,还可以构造出歌德巴赫猜想等数学上的未决问题。
所以,我们可以得出结论:判定lambda表达式相等,其难度是大于或等于解决这些数学上的未决问题的。
对于一个lambda函数,其归约的结果可能是具有β正规形式的lambda函数,亦可能是无限递归的含有不动点的函数。
某些形式上含有不动点的函数,尽管形式上无法归约到具有β正规形式的函数,但又能用某些数学方法论证其等价于具有β正规形式的lambda函数。
判定两个lambda函数是否相等,实际上就是lambda版本的图灵停机问题。
正因为停机问题无法通过图灵机解决,lambda函数等价问题无法通过lambda函数归约求解,程序员才有机会不断发现更好的算法。