λ演算
λ演算是数理逻辑和计算机科学中的一个形式系统,用于描述函数定义、函数应用和递归。它是计算理论的基础,为研究计算过程提供了一个简洁而强大的数学模型。下面,我将从基本概念开始,逐步深入讲解λ演算的核心内容。
第一步:基本组成元素
λ演算只包含三种表达式:
- 变量(Variables):例如 x, y, z。它们是表达式的基本符号。
- 抽象(Abstraction):用于定义函数。格式为
λx. M。它可以理解为“一个函数,其参数是x,函数体是M”。这里的λx.将变量x绑定(或“捕获”)在表达式M中。例如,λx. x表示一个“恒等函数”,即输入什么就输出什么。 - 应用(Application):表示将一个函数应用于一个参数。格式为
(M N)。它表示将函数M应用于参数N。例如,( (λx. x) y )表示将恒等函数应用于变量y。
第二步:括号约定与自由/绑定变量
为了简化书写,我们有一些括号约定:
- 函数应用是左结合的的:
M N P等价于((M N) P)。 λx. M N等价于λx. (M N),而不是(λx. M) N。函数体一直延伸到其右侧尽可能远的位置。
理解变量的作用域至关重要:
- 在
λx. M中,x是一个绑定变量。 - 一个在表达式中出现,但未被任何
λ绑定的变量,称为自由变量。 - 例如,在表达式
(λx. x y) (λz. z)中:- 第一个
x被λx绑定。 - 第二个
x是自由变量(它不属于任何函数的参数)。 y是自由变量。- 两个
z都被λz绑定。
- 第一个
第三步:核心操作——β-归约
β-归约是λ演算的“计算”引擎。它定义了如何执行函数应用。
规则是:一个应用 (λx. M) N 可以归约为 M[x := N]。
这个符号 M[x := N] 的意思是“将表达式 M 中所有自由出现的变量 x 替换为表达式 N”。
让我们看一个例子:归约 (λx. x x) y
- 识别出这是一个应用:函数是
(λx. x x),参数是y。 - 根据β-归约规则,我们将函数体
x x中的自由变量x替换为y。 - 替换后得到
y y。
所以,(λx. x x) y → y y。这个箭头→表示一次β-归约。
第四步:归约策略与Church-Rosser定理
当一个表达式中有多个地方可以进行β-归约时,就产生了归约策略的问题。主要有两种:
- 正则序归约:总是优先归约最外、最左边的可归约式。
- 应用序归约:总是优先归约最内、最左边的可归约式。
一个关键定理——Church-Rosser定理(或合流定理)指出:如果一个λ表达式可以通过不同的归约路径得到多个结果,那么这些结果最终必定可以再归约到同一个结果。这意味着归约的最终结果(如果存在)是唯一的,与所选的归约路径无关。这保证了计算的确定性。
第五步:编码数据与运算
λ演算看似只有函数,但它足够强大,可以表示所有可计算的数据和操作。这是通过编码实现的。
-
Church布尔值:
TRUE := λx. λy. x(选择第一个参数)FALSE := λx. λy. y(选择第二个参数)- 逻辑运算如
AND可以定义为:AND := λp. λq. p q FALSE。你可以验证AND TRUE FALSE会归约为FALSE。
-
Church数:用函数表示自然数。
0 := λf. λx. x(应用f零次于x)1 := λf. λx. f x(应用f一次于x)2 := λf. λx. f (f x)(应用f两次于x)- 后继函数
SUCC := λn. λf. λx. f (n f x)。例如,SUCC 1会归约为2。
第六步:递归与不动点组合子
如何在λ演算中定义递归函数(即调用自身的函数)?由于函数没有名字,我们无法直接通过名字调用自身。解决方案是使用不动点组合子。最著名的是 Y组合子:
Y := λf. (λx. f (x x)) (λx. f (x x))
Y组合子的关键特性是:对于任何函数 F,都有 Y F = F (Y F)。这意味着 Y F 是函数 F 的一个不动点。通过精巧地设计 F,我们可以让 Y F 表示我们想要的递归函数。例如,可以用它来定义阶乘函数。
λ演算虽然是上世纪30年代的发明,但它奠定了函数式编程语言(如Lisp, Haskell)的理论基础,并且是研究程序语义和计算复杂性的核心工具。