函数式编程的数学基础(五)lambda演算

我们前面的丘奇数推导中,已经掌握了一定的lambda演算技巧。接下来,是时候正式了解一下lambda演算了。

lambda的多种记法

因为时代原因,一些资料采用的lambda记法跟我们文章所写有一定区别,此处列出常见的记法。方便大家在查阅不同来源的资料时不至于混淆。

单字母命名lambda

在一些风格较为古老的资料中,只支持单字母参数名,这样,就会省略空格和点, λ \lambda λ符号后第一个字母为参数名,其后为表达式内容:
( λ x λ y x y ) z (\lambda x\lambda yxy)z (λxλyxy)z

以我们本系列的语法表示为:
( λ x . λ y . x y ) z (\lambda x.\lambda y.x\ y)\ z (λx.λy.x y) z

支持多参数

一些资料中,同样仅支持单字母作为lambda的参数名,但保留以点分割,这样可以支持柯里化的多参数记法,例如:
λ x y . ( x y ) \lambda xy.(x\ y) λxy.(x y)

以我们本系列的语法表示为:
λ x . λ y . ( x y ) \lambda x.\lambda y.(x\ y) λx.λy.(x y)

二者完全等价,尽管书写形式上是多参数,lambda只支持柯里化的多参数。

符号替换记法

一些资料中,会用一些记法来表示lambda替换的过程。
x : = y ( x z ) x:=y(x\ z) x:=y(x z)

表示在后面的表达式中,把x替换为y。

另外一些资料中,也会用斜杠来表示:
y / x ( x z ) y/x(x\ z) y/x(x z)

转换与归约规则

接下来我们来正式地介绍一下lambda演算的规则。

  • α转换:换元变换,直觉上,可以理解为当我们改变一个参数名时,只要用到此参数的部分都跟着改名,那么函数跟原来的函数等价。记为:

M ↠ α N M\ {\twoheadrightarrow}{\alpha}\ N \\ M ↠α N
λ x . x ↠ α λ y . y \lambda x.x\ {\twoheadrightarrow}
{\alpha}\ \lambda y.y λx.x ↠α λy.y

  • β归约:代入归约,直觉上,可以理解为我们调用一个函数时,把所有形参代换为实参进行化简。记为:

M ↠ β N M\ {\twoheadrightarrow}{\beta}\ N \\ M ↠β N
( λ x . x z ) y ↠ β ( y z ) (\lambda x.x\ z)\ y\ {\twoheadrightarrow}
{\beta}\ (y\ z) (λx.x z) y ↠β (y z)

  • η归约:调用归约,直觉上,可以理解为当一个函数内仅有另一个函数的调用,并透传参数时,实际上它们是同一个函数。记为:

M ↠ η N M\ {\twoheadrightarrow}{\eta}\ N \\ M ↠η N
λ x . y x ↠ η y \lambda x.y\ x\ {\twoheadrightarrow}
{\eta}\ y λx.y x ↠η y

Church-Rosser定理

有时,一个lambda表达式存在多个不同的归约方式,

Church-Rosser定理的形式化定义:
∀ 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 ∀M,N1,N2∈Λ:若有 M↠βN1 且 M↠βN2 则 ∃X∈Λ:N1↠βX 且 N2↠βX

我们把它画成图:
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} M↓βN2β β N1↓βX

我们对照图来解释,就是如果 N 1 N_1 N1和 N 2 N_2 N2存在,那么一定能找到一个 X X X。

直觉上,可以Church-Rosser定理理解为β归约的"殊途同归",即无论以何种顺序对lambda函数进行归约,最终可以得到一个一致的结果。

从直觉上来看,Church-Rosser定理的成立是显然的:对于一个"算式"来说,不论我们先计算它的哪个部分,最终都能得到同样的结果。

Lambda演算的Church-Rosser定理有几种不同的形式化证明方法,考虑到证明过程较为枯燥冗长,本篇就不在这里给出了,对数学有兴趣的的同学可以自行查阅相关资料。

Lambda演算的Church-Rosser定理还可以推广到 η \eta η归约,进而可以证明 β η \beta\eta βη归约混合运算也符合Church-Rosser定理。

Church-Rosser定理揭示了lambda演算的重要性质:

  • 并行特性,具有多个归约方向lambda演算可以依据其结构进行并行运算,以lambda为基础理论的计算机语言也有此性质。
  • 推导一致性,不论如何进行归约,lambda演算不会得到自相矛盾的结果。

从Church-Rosser定理,我们很容易想到lambda表达式可以通过β归约,达到无法β归约的形式,这个形式被称为β正规形式。由Church-Rosser定理可知,若β正规形式存在,它必定是唯一的。

β正规形式可以被视为lambda演算的最终计算结果。

那么,是否每一个lambda函数都存在β正规形式呢?

不动点

我们来研究一个特殊的函数。
Ω = ( λ x . x x ) ( λ x . x x ) Ω = (λx. x\ x)\ (λx. x\ x) Ω=(λx.x x) (λx.x x)

当我们尝试对Ω做β归约时,可以发现,Ω的β归约是它自身。因此,Ω被称作β归约的不动点(fix point)。显然,不动点Ω不存在β正规形式。

当我们加入一次函数调用,Ω函数就变成了Y组合子,它能够用于实现函数递归。
Y = λ f . ( λ x . f ( x x ) ) ( λ x . f ( x x ) ) Y = λf.(λx.f\ (x\ x))\ (λx.f\ (x\ x)) Y=λf.(λx.f (x x)) (λx.f (x x))

我们也可以稍作变通,少写一个 f f f,把它写作: Y = λ f . ( λ x . x x ) ( λ x . f ( x x ) ) Y = λf.(λx.x\ x)\ (λx.f\ (x\ x)) Y=λf.(λx.x x) (λx.f (x x)),。

这个形式与我们在除法一节推导出的Y组合子一致。经过一次β归约,就可以得到上面的形式。

具体到编程语言层面,考虑到Y组合子可能导致死循环,我们可以对 f f f的参数部分做逆η归约 ,形成 Z Z Z组合子。
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)) Z=λf.(λx.f (λz.x x z)) (λx.f (λz.x x z))

实际上,Y组合子的变体非常丰富,例如Haskell Curry本人发现的 Θ Θ Θ组合子。
Θ = λ f . ( λ x . λ y . f ( y x ) ) ( λ x . λ y . f ( y x ) ) Θ = λf.(λx.λy.f\ (y\ x))\ (λx.λy.f\ (y\ x)) Θ=λf.(λx.λy.f (y x)) (λx.λy.f (y x))

尽管这些含有不动点的组合子破坏了β正规形式,但它们又是表达复杂计算逻辑的必备。

这也从侧面说明,我们无法通过机械进行β归约,去解决复杂的计算问题。

判定相等

既然β正规形式这条路走不通,那么是否有一种通用的方案,能够判定lambda表达式相等呢?

在一些情况下,这个问题非常简单,比如,如果两个lambda函数包含的符号完全一致,那它们两个必定等价,能够用三种规则互相转换的lambda表达式页总是互相等价的。

但对于含有不动点的lambda函数,可就没那么简单了。

此处我们不做严格证明,从感性的角度来思考一下这个问题的难度。

我们前面教程已经构造了分支逻辑和丘奇数基本运算,并且通过Y组合子可以实现递归,此处我们就使用大家更熟悉的符号系统来构造一个函数:
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. f=λx.⎩ ⎨ ⎧1f(x∗3+1)f(x/2)if x==1if x%2 != 0,if x%2==0.

我们来判断它是否与 λ x . 1 \lambda x.1 λx.1 等价。所以问题就转化成:对所有自然数x,如果它是奇数,就把它乘以三加一,如果它是偶数,则把它除以2,一直这样做下去,是否每个自然数都能变成1?

这个问题实质上等价于一个著名的未能解决的数学猜想:角谷静夫猜想(Collatz猜想)。

只要你愿意,还可以构造出歌德巴赫猜想等数学上的未决问题。

所以,我们可以得出结论:判定lambda表达式相等,其难度是大于或等于解决这些数学上的未决问题的。

对于一个lambda函数,其归约的结果可能是具有β正规形式的lambda函数,亦可能是无限递归的含有不动点的函数。

某些形式上含有不动点的函数,尽管形式上无法归约到具有β正规形式的函数,但又能用某些数学方法论证其等价于具有β正规形式的lambda函数。

判定两个lambda函数是否相等,实际上就是lambda版本的图灵停机问题。

正因为停机问题无法通过图灵机解决,lambda函数等价问题无法通过lambda函数归约求解,程序员才有机会不断发现更好的算法。

相关推荐
fobwebs几秒前
Chrome谷歌浏览器多开教程,如何在电脑上同时登录多个GMAIL账号
前端·chrome·多开·同时登录多个gmail
前端 贾公子9 分钟前
小程序蓝牙打印探索与实践 (最终章)
前端·微信小程序·小程序
chushiyunen9 分钟前
vue export default
前端·javascript·vue.js
右耳朵猫AI13 分钟前
前端周刊2026W23 | React 19.2.7、Conductor重写提速、Lovable切换TanStack Start
前端·react.js·前端框架
copyer_xyf36 分钟前
FastAPI 项目骨架搭建
前端·后端·python
智码看视界42 分钟前
老梁聊全栈:CSS3 高级特性—Flex/Grid 布局体系深度解析
前端·css3·布局·flexbox·grid·工程实践·全栈工程师
IT_陈寒1 小时前
Python虚拟环境的这个坑,我居然绕了三天才爬出来
前端·人工智能·后端
matlab_xiaowang1 小时前
WeasyPrint:把 HTML 变成 PDF 的文档工厂
前端·其他·pdf·html
星栈独行1 小时前
写 Makepad Demo 不难,难的是把它写成项目
前端·程序人生·ui·rust
深圳恒讯1 小时前
非洲服务器延迟高吗?实测数据与场景化解读
运维·服务器·前端