编译器的数学基础

本文介绍了Lambda演算的基本概念及其在递归定义中的应用。Lambda演算是一种用于研究函数定义、函数应用和递归的形式系统,可用于清晰地定义可计算函数。文中详细解释了如何利用Lambda演算表达自然数、逻辑与谓词、有序对等概念,并通过Y组合子实现递归。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近,在看compiler代码的同时,学习一下compiler的基础知识。
计算机的数学基础是图灵机,其实Lambda Calculus(λ算子)证明与图灵机等价。
Lambada Calculus用数学语言表示,图灵机的表述更加直观,不过两者是等价。
 
什么是计算呢?说到底是数据的变换,从状态A到状态B,通过数学建模,现实域里面的问题变换成数学域里面的表述。
数学域里面的计算完成之后,返回现实域得到实际问题的解答。
计算理论是数学的一个分支,用来进行变换的工具,计算机就是计算理论的载体。
以前一直不太理解啥是递归,看了Lambada Calculus之后,发现递归原来只是一种语义的表述形式,而不是蕴含真正的计算过程。
不过,对于Lambada Calculus的正确性就不要多想了,再想下去也没用,不可能想明白的嘛~~
 
上面纯属个人理解,下面是wiki的解释,比我的理解可信度高很多。
 
 
λ演算lambda calculus)是一套用于研究 函数 定义、函数应用和 递归 形式系统 。它由 丘奇 (Alonzo Church)和他的学生 克莱尼 (Stephen Cole Kleene)在 20世纪 30年代 引入。Church 运用λ演算在 1936年 给出 判定性问题 (Entscheidungsproblem)的一个否定的答案。这种演算可以用来清晰地定义什么是一个 可计算函数 。关于两个 lambda 演算表达式是否等价的命题无法通过一个“通用的算法”来解决,这是不可判定性能够证明的头一个问题,甚至还在 停机问题 之先。Lambda 演算对 函数式编程 有巨大的影响,比如 Lisp 语言 ML 语言 Haskell 语言
Lambda 演算可以被称为最小的通用程序设计语言。它包括一条变换规则(变量替换)和一条函数定义方式,Lambda 演算之通用在于,任何一个可计算函数都能用这种形式来表达和求值。因而,它是等价于 图灵机 的。尽管如此,Lambda 演算强调的是变换规则的运用,而非实现它们的具体机器。可以认为这是一种更接近软件而非硬件的方式。
本文讨论的是 Church 的“无类型 lambda 演算”,此后,已经研究出来了一些 有类型 lambda 演算
 
历史
最开始,Church 试图创制一套完整的形式系统作为数学的基础,当他发现这个系统易受 罗素悖论 的影响时,就把 lambda 演算单独分离出来,用于研究 可计算性 ,最终导致了他对 判定性问题 的否定回答。
 
非形式化的描述
在 lambda 演算中,每个表达式都代表一个只有单独参数的函数,这个函数的参数本身也是一个只有单一参数的函数,同时,函数的值是又一个只有单一参数的函数。函数是通过 lambda 表达式匿名地定义的,这个表达式说明了此函数将对其参数进行什么操作。例如,“加 2”函数 f(x) = x + 2 可以用 lambda 演算表示为 λ x. x + 2 (λ y. y + 2 也是一样的,参数的取名无关紧要) 而 f(3) 的值可以写作 (λ x. x + 2) 3。函数的作用 (application) 是 左结合 的:f x y = (f x) y。考虑这么一个函数:它把一个函数作为参数,这个函数将被作用在 3 上:λ f. f 3。如果把这个 (用函数作参数的) 函数作用于我们先前的“加 2”函数上:(λ f. f 3) (λ x. x+2),则明显地,下述三个表达式:
f. f 3) (λ x. x+2)   与    (λ x. x + 2) 3    与    3 + 2
是等价的。有两个参数的函数可以通过 lambda 演算这么表达:一个单一参数的函数的返回值又是一个单一参数的函数 (参见 Currying )。例如,函数 f(x, y) = x - y 可以写作 λ x. λ y. x - y。下述三个表达式:
x. λ y. x - y) 7 2    与    (λ y. 7 - y) 2    与    7 - 2
也是等价的。然而这种 lambda 表达式之间的等价性无法找到一个通用的函数来判定。
并非所有的 lambda 表达式都可以规约至上述那样的确定值,考虑
x. x x) (λ x. x x)
x. x x x) (λ x. x x x)
然后试图把第一个函数作用在它的参数上。 (λ x. x x) 被称为 ω 组合子 ,((λ x. x x) (λ x. x x)) 被称为 Ω,而 ((λ x. x x x) (λ x. x x x)) 被称为 Ω2,以此类推。
若仅形式化函数作用的概念而不允许 lambda 表达式,就得到了 组合子逻辑
 
形式化定义
形式化地,我们从一个标识符 (identifier) 的 可数无穷 集合 开始,比如 {a, b, c, ..., x, y, z, x1, x2, ...},则所有的 lambda 表达式可以通过下述以 BNF 范式表达的 上下文无关文法 描述:
  1. <expr> ::= <identifier>
  2. <expr> ::= (λ <identifier> . <expr>)
  3. <expr> ::= (<expr> <expr>)
头两条规则用来生成函数,而第三条描述了函数是如何作用在参数上的。通常,lambda 抽象 (规则 2) 和函数作用 (规则 3) 中的括号在不会产生歧义的情况下可以省略。如下假定保证了不会产生歧义:(1) 函数的作用是左结合的,和 (2) lambda 操作符被绑定到它后面的整个表达式。例如,表达式 ((λ x. (x x)) (λ y. y)) 可以简写成 (λ x. x x) λ y.y
类似 λ x. (x y) 这样的 lambda 表达式并未定义一个函数,因为变量 y 的出现是自由的,即它并没有被绑定到表达式中的任何一个 λ 上。变量出现次数的绑定是通过下述规则 (基于 lambda 表达式的结构 归纳 地) 定义的:
  1. 在表达式 V 中,V 是变量,则这个表达式里变量 V 只有一次自由出现。
  2. 在表达式 λ V . E 中 (V 是变量,E 是另一个表达式),变量自由出现的次数是 E 中变量自由出现的次数,减去 EV 自由出现的次数。因而,E 中那些 V 被称为绑定在 λ 上。
  3. 在表达式 (E E' ) 中,变量自由出现的次数是 EE' 中变量自由出现次数之和。
在 lambda 表达式的集合上定义了一个 等价关系 (在此用 == 标注),“两个表达式其实表示的是同一个函数”这样的直觉性判断即由此表述,这种等价关系是通过所谓的“alpha-变换规则”和“beta-归约规则”。

α-变换
Alpha-变换规则表达的是,被绑定变量的名称是不重要的。比如说 λx.x 和 λy.y 是同一个函数。尽管如此,这条规则并非像它看起来这么简单,关于被绑定的变量能否由另一个替换有一系列的限制。
Alpha-变换规则陈述的是,若 VW 均为变量,E 是一个 lambda 表达式,同时 E[V:=W] 是指把表达式 E 中的所有的 V 的自由出现都替换为 W,那么在 W 不是 E 中的一个自由出现,且如果 W 替换了 VW 不会被 E 中的 λ 绑定的情况下,有
λ V. E == λ W. E[V:=W]
这条规则告诉我们,例如 λ x. (λ x. x) x 这样的表达式和 λ y. (λ x. x) y 是一样的。
 
β-归约
Beta-归约规则表达的是函数作用的概念。它陈述了若所有的 E' 的自由出现在 E [V:=E' ] 中仍然是自由的情况下,有
((λ V. E ) E' ) == E [V:=E' ]
成立。
== 关系被定义为满足上述两条规则的最小等价关系 (即在这个等价关系中减去任何一个映射,它将不再是一个等价关系)。
对上述等价关系的一个更具操作性的定义可以这样获得:只允许从左至右来应用规则。不允许任何 beta 归约的 lambda 表达式被称为Beta范式。并非所有的 lambda 表达式都存在与之等价的范式,若存在,则对于相同的形式参数命名而言是唯一的。此外,有一个算法用户计算范式,不断地把最左边的形式参数替换为实际参数,直到无法再作任何可能的规约为止。这个算法当且仅当 lambda 表达式存在一个范式时才会停止。 Church-Rosser 定理 说明了,当且仅当两个表达式等价时,它们会在形式参数换名后得到同一个范式。
 
η-变换
前两条规则之后,还可以加入第三条规则,eta-变换,来形成一个新的等价关系。Eta-变换表达的是 外延性 的概念,在这里外延性指的是,两个函数对于所有的参数得到的结果都一致,当且仅当它们是同一个函数。Eta-变换可以令 λ x . f xf 相互转换,只要 x 不是 f 中的自由出现。下面说明了为何这条规则和外延性是等价的:
fg 外延地等价,即,f a == g a 对所有的 lambda 表达式 a 成立,则当取 a 为在 f 中不是自由出现的变量 x 时,我们有 f x == g x,因此 λ x . f x == λ x . g x,由 eta-变换 f == g。所以只要 eta-变换是有效的,会得到外延性也是有效的。
相反地,若外延性是有效的,则由 beta-归约,对所有的 y 有 (λ x . f x) y == f y,可得 λ x . f x == f,即 eta-变换也是有效的。
 
lambda 演算中的运算
在 lambda 演算中有许多方式都可以定义 自然数 ,但最常见的还是 邱奇数 ,下面是它们的定义:
0 = λ f. λ x. x
1 = λ f. λ x. f x
2 = λ f. λ x. f (f x)
3 = λ f. λ x. f (f (f x))
以此类推。直观地说,lambda 演算中的数字 n 就是一个把函数 f 作为参数并以 fn 次幂为返回值的函数。换句话说,Church 整数是一个 高阶函数 -- 以单一参数函数 f 为参数,返回另一个单一参数的函数。
(注意在 Church 原来的 lambda 演算中,lambda 表达式的形式参数在函数体中至少出现一次,这使得我们无法像上面那样定义 0) 在 Church 整数定义的基础上,我们可以定义一个后继函数,它以 n 为参数,返回 n + 1:
SUCC = λ n. λ f. λ x. f (n f x)
加法是这样定义的:
PLUS = λ m. λ n. λ f. λ x. m f (n f x)
PLUS 可以被看作以两个自然数为参数的函数,它返回的也是一个自然数。你可以试试验证
PLUS 2 3    与    5
是否等价。乘法可以这样定义:
MULT = λ m. λ n. m (PLUS n) 0,
m 乘以 n 等于在零的基础上 n 次加 m。另一种方式是
MULT = λ m. λ n. λ f. m (n f)
正整数 n 的前驱元 (predecessesor) PRED n = n - 1 要复杂一些:
PRED = λ n. λ f. λ x. ng. λ h. h (g f)) (λ u. x) (λ u. u)
或者
PRED = λ n. ng. λ k. (g 1) (λ u. PLUS (g k) 1) k) (λ l. 0) 0
注意 (g 1) (λ u. PLUS (g k) 1) k 表示的是,当 g(1) 是零时,表达式的值是 k,否则是 g(k) + 1。
 
逻辑与谓词
习惯上,下述两个定义 (称为 Church 布尔值) 被用作 TRUEFALSE 这样的布尔值:
TRUE := λ x y. x
FALSE := λ x y. y
(注意 FALSE 等价于前面定义邱奇数零)
接着,通过这两个 λ-项,我们可以定义一些逻辑运算:
AND := λ p q. p q FALSE
OR := λ p q. p TRUE q
NOT := λ p. p FALSE TRUE
IFTHENELSE := λ p x y. p x y
我们现在可以计算一些逻辑函数,比如:
AND TRUE FALSE
≡ (λ p q. p q FALSE) TRUE FALSE →β TRUE FALSE FALSE
≡ (λ x y. x) FALSE FALSE →β FALSE
我们见到 AND TRUE FALSE 等价于 FALSE
“谓词”是指返回布尔值的函数。最基本的一个谓词是 ISZERO,当且仅当其参数为零时返回真,否则返回假:
ISZERO := λ n. nx. FALSE) TRUE
运用谓词与上述 TRUEFALSE 的定义,使得 "if-then-else" 这类语句很容易用 lambda 演算写出。
 
有序对
有序对(2-元组)数据类型可以用 TRUEFALSEIF 来定义。
CONS := λ x y. λ p. IF p x y
CAR := λ x. x TRUE
CDR := λ x. x FALSE
链表数据类型可以定义为,要么是为空列表保留的值(e.g. FALSE),要么是 CONS 一个元素和一个更小的列表。
 
递归
递归 是使用函数自身的函数定义;在表面上,lambda 演算不允许这样。但是这种印象是误解。考虑个例子, 阶乘 函数 f(n) 递归的定义为
f(n) := if n = 0 then 1 else n·f(n-1)
在 lambda 演算中,你不能定义包含自身的函数。要避免这样,你可以开始于定义一个函数,这里叫 g,它接受一个函数 f 作为参数并返回接受 n 作为参数的另一个函数:
g := λ f n. (if n = 0 then 1 else n·f(n-1))
函数 g 返回要么常量 1,要么函数 fn-1n 次应用。使用 ISZERO 谓词,和上面描述的布尔和代数定义,函数 g 可以用 lambda 演算来定义。
但是,g 自身仍然不是递归的;为了使用 g 来建立递归函数,作为参数传递给 gf 函数必须有特殊的性质。也就是说,作为参数传递的 f 函数必须展开为调用带有一个参数的函数 g -- 并且这个参数必须再次 f 函数!
换句话说,f 必须展开为 g(f)。这个到 g 的调用将接着展开为上面的阶乘函数并计算下至另一层递归。在这个展开中函数 f 将再次出现,并将被再次展开为 g(f) 并继续递归。这种函数,这里的 f = g(f),叫做g 的不动点,并且它可以在 lambda 演算中使用叫做悖论算子不动点算子来实现,它被表示为 Y -- Y 组合子 :
Y = λ g. (λ x. g (x x)) (λ x. g (x x))
在 lambda 演算中,Y gg 的不动点,因为它展开为 g (Y g)。现在,要完成我们对阶乘函数的递归调用,我们可以简单的调用  g (Y g) n,这里的 n 是我们要计算它的阶乘的数。
比如假定 n = 5,它展开为:
n.(if n = 0 then 1 else n·((Y g)(n-1)))) 5
if 5 = 0 then 1 else 5·(g(Y g)(5-1))
5·(g(Y g) 4)
5·(λ n. (if n = 0 then 1 else n·((Y g)(n-1))) 4)
5·(if 4 = 0 then 1 else 4·(g(Y g)(4-1)))
5·(4·(g(Y g) 3))
5·(4·(λ n. (if n = 0 then 1 else n·((Y g)(n-1))) 3))
5·(4·(if 3 = 0 then 1 else 3·(g(Y g)(3-1))))
5·(4·(3·(g(Y g) 2)))
...
等等,递归的求值算法的结构。所有递归定义的函数都可以看作某个其他适当的函数的不动点,因此,使用 Y 所有递归定义的函数都可以表达为 lambda 表达式。特别是,我们现在可以明晰的递归定义自然数的减法、乘法和比较谓词。
 
可计算函数和 lambda 演算
自然数 的函数 F: NN 可计算函数 当且仅当 存在着一个 lambda 表达式 f,使得对于 N 中的每对 x, y 都有 F(x) = y 当且仅当 f x == y,这里的 xy 分别是对应于 xy 邱奇数
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值