迭代组合子的构造 - LCaC § 4C, Theorem 4.20

这篇博客对 Lambda-Calculus and Combinator § 4C Theorem 4.20 的 proof 部分做一些补充,证明的整体意图是证明符合 Definition 4.18 的全称递归函数可以用组合子来表示. 这里对该证明的关键部分,即迭代组合子的构造,做简要的陈述. 也可以理解为:在 Pure Lambda Calculus 中编写一个具有特定终止条件的迭代程序.

递归与迭代:在 $R_{\text{Bernays}}$ 递归组合子的构造中,求解某个递归函数 $\phi $ 的值 $ \phi(n) $,一种可行的方法是,从 $ \phi(0) $ 开始,做 $n$ 次迭代得到 $ \phi(n) $(大致如下:记递归步更新函数为 $\chi$,$\phi(n) = \chi^n \phi(0)$). 迭代和递归是正向与逆向的区别. 本文中的"递归"意在描述"调用自身" 的结构特点,探讨的中心问题还是对迭代($0 \rightarrow 1 \rightarrow … \rightarrow n$)问题的求解.

构造目标

假设有一个检查函数 $X$,迭代的终止条件为 $XY=_{\beta,w}\bar{0}$, 我们希望构造一个组合子 $P$,得到最小的符合终止条件的 $Y$. 即从 $Y = \bar{0}$ 开始,检查 $(XY) = _{\beta,w}? \bar{0}$ ,若条件满足,则返回值为此 $Y$,否则继续检查 $X(\bar{\sigma}Y)$,我们希望构造一个组合子 $P$ 自动化此检查过程,形式化地,我们预期 $P$ 的行为如下:

$$ PXY =_{\beta,w}Y \quad \text{, if } XY = _{\beta,w}\bar{0};$$

$$ PXY =_{\beta,w}PX(\bar{\sigma}Y), \text{ otherwise} $$

完全尊重预期,写一个 $P$ 组合子:$P \equiv \lambda xy.\textbf{D}y(Px(\bar{\sigma}y))(xy)$,其中 $\textbf{D}=\lambda xyz.z(\textbf{K}y)x$, 可以用 $\textbf{Y}$ 组合子来 fix 这个 $P$($P \equiv \textbf{Y}(\lambda pxy.\textbf{D}y(px(\bar{\sigma}y))(xy))$),使用 $\textbf{Y} $ 组合子求解出的 $P$ 没有 normal form,这里不采用此 $P$,我们尝试逐层构造一个具备 normal form 的 $P$ 组合子.

构造过程

基本结构

用高级语言的伪代码表示现有的 $P$:

combinator p(x,y):            // L1: define p
    if (xy == 0):
        return const(y)                     
    else:
        return p(x, σ y)       // L5: call p

递归的 $\lambda $,形如 $P \equiv \lambda x. MPN $,像这样的 $\lambda$-term,符合我们在高级语言程序设计的经验,但是不符合 Lambda Calculus 中的规范,因为 Lambda Calculus 对 $\lambda$-term 的归纳定义并不包含为 abstraction 赋标识符的规则,我们写 $P$ 等标识符的目的只在于提升可读性和明确表达式结构,而不是借助标识符的复用来像高级语言编程一样定义递归函数. 一个细节是,我们在书里看到的为某个 $\lambda$-term 记标识符用的符号是 $\equiv$ 而不是 $=$.

我们不能通过标识符的复用定义递归不意味着我们不能定义递归,只是我们需要依赖多一层的抽象来构造出 形式上的非递归,事实意义上的递归.

从高级语言编程的角度出发,如果我们希望用某种方式取代 L5 中对 p 自身的调用,我们可以如下修改我们的代码. 为了两个分支的结构一致,我们设计一个函数列表 tt 中的两个函数对应两个分支,函数 getCurrentY 将返回当前 y 值,recursion_p 将承担递归的工作:(这里先不考虑函数的具体结构和参数设计/参数传递的问题,后面总有办法的,这里我们只关心整体结构)

t = [getCurrentY, recursion_p]      // list of functions

combinator p(x,y):            
    if (xy == 0):
        return t[0]                     
    else:
        return t[1]    

如何在 Lambda Calculus 中表达以上的代码?为了专注于我们目前处理的抽象层次,先将 $P \equiv \lambda xy.\textbf{D}y(Px(\bar{\sigma}y))(xy)$ 的结构简化为 $P \equiv \lambda xy.\textbf{D}AB(xy)$

根据预期 / 上面的伪代码,我们可以写出大致的 $\lambda $ 框架如下:

$$ P \equiv \lambda xy. T(xy)[params] $$ $$ T \equiv \textbf{D}AB $$ $$ A \equiv \lambda [params]. \dots$$ $$ B \equiv \lambda [params]. …$$

此时 $PXY = _{\beta,w} T(XY) $,$ XY = _{\beta, w} \bar{0} \longrightarrow PXY = A; XY \neq _{\beta, w} \bar{0} \longrightarrow PXY = B $ .

我们预期:将分支的具体逻辑放在 $T$ 中,将分支的选择和分支函数参数传递放在 $P$ 中.

具体细节

上面我们忽略了许多细节,现在是考虑细节的时候了 :)

首先注意一个事实:我们将分支函数参数传递的工作放在 $P$ 中,意味着无论当前 $P$ 中的 $(xy)$ 将导向哪个分支,我们传递的参数列表都是一致的 / 都只能是一致的. 因为 $(xy) = _{\beta,w}\bar{0}$ 对应的情况更简单,只需返回当前 $y$,所以我们延后考虑这一情况,先考虑 $(xy) ≠ _{\beta,w}\bar{0}$ 的情况,让前者迁就后者(,因为两个分支都与 $y$ 有关,所以参数列表中必然有 $y$,在这一点上两个分支是有共性的;另一方面,我们甚至可以两个分支函数对应的参数并列传递,然后在 $A$,$B$ 的具体实现中 不对与本分支无关的参数进行绑定).

递归(迭代)分支的构造

目标是:$XY \neq _{\beta, w} \bar{0} \longrightarrow PXY = B \xlongequal{\text{expected}} PX(\bar{\sigma}Y) $,我们希望函数 $B$ 与 $P$ 中传递的 $[params]$ 应用后得到的 $\lambda$-term 和 $PXY$ 具备一样的结构(注意不是 $B$ 和 $P$ 两个 abstraction 本身结构一致),只是 $Y$ 位置的值替换成了 $(\bar{\sigma}Y)$,为了得到和 $P$ 一样的结构,最简单的方法是——把 $P$ 现有的组件作为 $[params]$ 传递到 $B$(,当然也传递到了 $A$),然后在 $B$ 中把这些组件重组成 $P$ 的结构:

$$ P \equiv \lambda xy. T(xy)Txy $$ $$ T \equiv \textbf{D}AB $$ $$ A \equiv \lambda tuv. \dots$$ $$ B \equiv \lambda tuv. q(uv) $$

($ T \mapsto t, x \mapsto u, y \mapsto v$)

由于 $[params]$ 的传递,现在 $P$ 的结构发生了改变,我们需要让 $B$ 与新的 $P$ 结构同步;另一方面,我们需要在 $B$ 的内部将传递进来的 $y$ (由 $v$ 绑定)变为 $\bar{\sigma}y$:

$$ P \equiv \lambda xy. T(xy)Txy $$ $$ T \equiv \textbf{D}AB $$ $$ A \equiv \lambda tuv. \dots$$ $$ B \equiv \lambda tuv. q(u(\bar{\sigma}v))qu(\bar{\sigma}v) $$

做一个检查:当 $XY \neq _{\beta, w} \bar{0}$:

$ \quad PXY $
$ = _{\beta,w} T(XY)TXY $
$ = _{\beta,w} BTXY $
$ = _{\beta,w} T(X(\bar{\sigma}Y))TX(\bar{\sigma}Y) $
$ = _{\beta,w} PX(\bar{\sigma}Y) $

符合我们的预期,至此,迭代分支的构造就完成了.

迭代终止分支的构造

目标是:$XY = _{\beta, w} \bar{0} \longrightarrow PXY = A \xlongequal{\text{expected}} Y $,在 $B$ 的构造过程中,我们已有的参数列表是 $ t \mapsto T, u \mapsto x, v \mapsto y$,在 $A$ 中,我们只需要把 $y$ 提取出来即可,所以 $A = \lambda tuv.v$ ,如果你希望和 Definition 4.8 的记法保持一致,那么 $A = \Pi^3_3$.

完整 $\lambda$

$$ P \equiv \lambda xy. T(xy)Txy $$ $$ T \equiv \textbf{D}AB $$ $$ A \equiv \lambda tuv. v (= _{\beta,w} \Pi^3_3) $$ $$ B \equiv \lambda tuv. q(u(\bar{\sigma}v))qu(\bar{\sigma}v) $$

LCaC Theorem 4.20 中的 $P$ 简述

LCaC Theorem 4.20 中给出的 $P$ 定义如下:

$$ T \equiv \lambda x.\textbf{D}\bar{0}(\lambda uv.u(x(\bar{\sigma}v))u(\bar{\sigma}v)) $$ $$ P \equiv \lambda xy.Tx(xy)(Tx)y $$

以与前文一致的格式转写:

$$ P \equiv \lambda xy.Tx(xy)(Tx)y $$ $$ T \equiv \lambda x.\textbf{D}AB $$ $$ A \equiv \bar{0} $$ $$ B \equiv \lambda uv.u(x(\bar{\sigma}v))u(\bar{\sigma}v) $$

在一些时刻把 $(Tx)$ 作为一个整体,让表达式更简洁了一些,同时让 $x$ 和 $T$ 有绑定关系($T \equiv \lambda x….$),在 $T$ 的内部依然可以单独地拿出 $x$ 使用;$P$ 中的 $(Tx)$ 闭包,使得传参的形态是 $B(Tx)y$ —— 而不是 $BTxy$ ——让 $A$ 的设计更简洁了,$A \equiv \bar{0}$.

虽然在细节上略有差别,但是整体结构与本文给出的 $P$ 是一致的.

其他

尝试着展开上面的 $P$:

$$ \lambda xy.\lambda x.\textbf{D}\bar{0}(\lambda uv.u(x(\bar{\sigma}v))u(\bar{\sigma}v))x(xy)(\lambda x.\textbf{D}\bar{0}(\lambda uv.u(x(\bar{\sigma}v))u(\bar{\sigma}v))x)y$$

如果你愿意把以下也全部展开:
$ \textbf{D} = _{\beta, w} \lambda xyz.z(\textbf{K}y)x, \quad \textbf{K} = _{\beta, w} \lambda xy.x $
$ \bar{0} = _{\beta, w} \lambda xy.y $
$ \bar{\sigma} = _{\beta, w} \lambda nfz. f (n f z)$

$$ \lambda xy.\lambda x.(\lambda xyz.z((\lambda xy.x)y)x)(\lambda xy.y)(\lambda uv.u(x(\lambda nfz. f (n f z)))u(\lambda nfz. f (n f z)))x(xy)(\lambda x.(\lambda xyz.z((\lambda xy.x)y)x)(\lambda xy.y)(\lambda uv.u(x(\lambda nfz. f (n f z)))u(\lambda nfz. f (n f z)))x)y$$

可以说,我们用这一堆符号 + Pure Lambda Culculus 的演算规则完成了一个迭代程序的构造——Programming in Pure Lambda Calculus.


Wish You a Nice Day!
Built with Hugo
Theme Stack designed by Jimmy