Skip to content

不动点组合子与递归

Fixed-point combinator · Y combinator · Z combinator

在无类型 λ 演算中把函数送到它自身的不动点,从而在没有递归原语时表达递归。

形式陈述

给定函数 f,若 x=f(x),则 xf 的不动点。不动点组合子则是一个闭合 λ 项 Φ,它对任意项 f 都满足

Φf=βf(Φf),

也就是 Φf 经有限次β-归约可与 f(Φf) 相互转换。Curry 的 Y 组合子为

Yλf.(λx.f(xx))(λx.f(xx)).

对任意 f,完整展开为

Yfβ(λx.f(xx))(λx.f(xx))βf((λx.f(xx))(λx.f(xx)))=f(Yf).

这个等式只保证组合子产生一个不动点,不保证 f 的不动点唯一,更不保证任意求值策略都能算出它。正常序可先展开最外层的 f传值调用却会在把参数交给 f 前不断求值 MfMf。适合传值求值的一个变体是

Zλf.(λx.f(λv.xxv))(λx.f(λv.xxv)).

对已经是值的 fZf 可求值到 f(λv.Zfv);额外的 λ 抽象把下一次自展开推迟到递归函数真正接收参数时。这是传值语义下的延迟不动点性质,不应与 Yff(Yf) 的正常序展开逐步混写。

直觉

递归定义常写成“函数在函数体里调用自己”,但这里没有函数名可供自引用。组合子改写问题:先把递归体写成一个接收 self 的普通高阶函数 g,再寻找满足 h=g(h) 的函数 hYg 正是这样的 h。自应用 xx 并不是递归业务逻辑,而是一套把当前生成器重新送回自身的布线装置。

这种构造揭示了语法递归与语义不动点的联系,却没有把二者合并。Y无类型 λ 演算中的项;最小不动点语义则在带序的语义域中选择由有限近似得到的最小解。前者提供递归表达式,后者解释递归方程为何代表特定程序含义。

例子与边界

把阶乘的递归体写成

Gλself.λn.if(n=0)then1elsenself(n1).

在正常序下,YGG(YG),因此 (YG)3 只在进入非零分支后才继续展开下一层 YG,最终得到 6。同一个 Y 直接放进严格语言时,运行会困在自应用的无限展开中,尚未进入 G 的条件分支;改用 ZG 才把这次展开藏到参数 λ 后面。

类型边界同样关键。若一般不动点组合子在简单类型 λ 演算中有类型 (AA)A,取恒等函数即可构造良类型的无限展开项,这与简单类型 λ 演算的强正规化矛盾。因此纯简单类型系统不能给一般 Y 定型;带一般递归的语言必须加入固定点原语、递归类型或其他足以打破该终止结论的机制。

推论与应用

不动点组合子说明“递归”不是无类型函数计算必须额外假设的语法能力:只靠抽象、应用和自应用就能编码递归。它也让传名调用与传值调用的差别变得可观察——二者共享 β-规则,却对先展开函数体还是先求实参作出不同选择。

在编程语言中,具名 let recfix 通常比直接展开 Y/Z 更适合作为源语言构造,因为实现可以分配递归闭包并保留清晰的调试信息。证明递归程序性质时,还需另行给出终止度量、归纳原理或域论解释;组合子本身只建立递归方程,不自动提供终止性、唯一性或最小性。

参考资料
  • H. B. Curry and Robert Feys, Combinatory Logic, Vol. I, North-Holland, 1958,fixed-point combinators。
  • Henk Barendregt, The Lambda Calculus: Its Syntax and Semantics, revised ed., North-Holland, 1984,fixed-point combinators and reduction strategies。
  • Benjamin C. Pierce, Types and Programming Languages, MIT Press, 2002,untyped recursion and typed fixed-point operators。