Kleene 递归定理
Kleene 递归定理
在本文中,我将给出 Kleene 递归定理 详细且严谨的证明。通过逐步剖析证明中的微妙细节,我们不仅让结果显得扎实且直观,也得以一窥计算的若干核心思想。
与大多数计算理论中的定理一样,该结果可以推广到所有计算模型。它并非针对某个特定实现的行为,而是关于计算本身普遍性的结构。
在你跟随我们一起走完证明之后,我希望不仅呈现一个优美的证明,还能传达一个哲学视角:这个结果意味着在足够强大的形式系统中,自指是不可避免的。一旦一个系统能够在 形 层面描述自身,它也必然能够在 元 层面描述自身,这是一个自然且无法回避的结果。这既非技巧、亦非比喻,更非仅仅哲学层面的遐想;然而,为了真正理解这一陈述为何确实为真,我们必须深入数学细节。
图灵机的功能
图灵机接受一个二进制字符串作为输入,执行若干计算,最终输出另一个二进制字符串。
- 我们记作 \(A(x)=y\) 表示机器 \(A\) 在输入 \(x\) 时停机最终输出 \(y\)。若 \(A\) 在 \(x\) 上不停机,我们记作 \(A(x)=\bot\)。
- 若机器 \(A\) 在所有输入上都停机,则称 \(A\) 为 全机(total)。
- 若机器 \(A\) 在某些输入上不停机,则称 \(A\) 为 偏机(partial)。
若两台机器 \(A\) 与 \(B\) 在所有输入上产生相同输出,则称它们 功能等价,即对所有 \(x\in\{0,1\}^*\),都有 \(A(x)=B(x)\)。
机器代码 —— 程序与数据同质
我们假设存在一种 良性 的编码方案,即单射且双向可计算。
- 编码 算子 \(\downarrow\),写作 \(A_\downarrow = a\),将机器 \(A\) 映射到其唯一的二进制串代码 \(a\)。
- 解码 算子 \(\uparrow\),写作 \(a_\uparrow = A\),将二进制串 \(a\) 解释为机器 \(A\) 的代码(若 \(a\) 有效)。
我们必须实现以下模块:编码模块(将机器映射到其唯一有效代码)、验证模块(检查代码是否有效)和解码模块(将有效代码还原为机器)。
若某串 \(a\) 并非有效代码,则定义解码后得到的机器 \(A = a_\uparrow\) 对所有输入均不停机,即对所有 \(x\in\{0,1\}^*\),有 \(A(x)=\bot\)。
对于两段代码 \(a, b\),若解码后得到的机器 \(A = a_\uparrow\) 与 \(B = b_\uparrow\) 功能等价,则记作 \(a \cong b\)。
Kleene 递归定理 —— 变换的不动点
在以下各节中,我们常用大写字母与对应小写字母来分别表示机器与其代码(例如 \(M\) 与 \(m\),\(X\) 与 \(x\),\(Y\) 与 \(y\))。
定理陈述
对于任意全机 \(T\)(称作 变换器),存在一台机器 \(Y\)(可以是偏机)及其代码 \(y\),使得
换言之,\(y\)(作为代码)在功能等价意义下,是变换 \(T\) 的不动点。
定理证明
-
构造一台全机 \(D\)(称作 对角机),满足对所有代码 \(a\in\{0,1\}^*\):
\[D(a) \cong A(a). \] -
定义全机
\[X = T \circ D, \]即
\[X(a) = T\bigl(D(a)\bigr), \]是一种函数复合。
-
设 \(x\) 为 \(X\) 的代码,取
\[y = D(x). \]则
\[y = D(x) \cong X(x) = T\bigl(D(x)\bigr) = T(y). \]因此
\[y \cong T(y), \]即 \(y\) 是 \(T\) 的不动点(功能等价意义下)。
核心所在
关键在于如何构造一个全机 \(D\)。
若天真的令 \(D(a)=A(a)\),则需让 \(D\) 模拟 \(A\) 在 \(a\) 上的运算,这可能不会停机,从而令 \(D\) 成为偏机。正确做法是令 \(D(a) \cong A(a)\),即 \(D(a)\) 输出一个 封装机 \(Q\) 的代码,该封装机在任意输入 \(w\) 上,其行为都复刻 \(A(a)_\uparrow\) 在 \(w\) 上的行为。
具体的,输入 \(a\) 时,全机 \(D\) 构造如下机器 \(Q\) 的描述:
- 解码 \(a\) 以还原机器 \(A\);
- 模拟 \(A\) 在输入 \(a\) 上运行,得到代码 \(b = A(a)\);
- 解码 \(b\) 以还原机器 \(B\);
- 对任意输入 \(w\),模拟 \(B\) 在 \(w\) 上运行,并输出相同的结果 \(B(w)\)。
最终,\(D\) 输出 \(q\),即 \(Q\) 的代码。此构造保证 \(D\) 在每个 \(a\) 上均能停机,故 \(D\) 为全机。由于 \(D\) 并未真正运行 \(A\)、\(B\) 或 \(Q\) 的任何主体计算,仅仅是写出 \(Q\) 的代码(该代码包含两个解码与两个模拟模块),故保证了全性。
由此,\(Q\) 与 \(A(a)_\uparrow\) 在所有输入上表现一致,遂得
在整个证明中,这一对角机的构造是唯一真正「落脚于」计算模型本身的部分。除了编码与解码之外,该构造还要求模型能模拟任意机器在任意输入上的运行,而这正是赋予计算以强大力量的核心能力。
自指程序 —— 获取自身机器代码
在本节中,当我们说 \(M[v]\) 是一台单输入机器,意思是 \(M\) 是一台单输入机器,该输入称为 \(v\)。类似的,当我们说 \(F[u,v]\) 是一台双输入机器,意思是 \(F\) 是一台双输入机器,这两个输入称为 \(u, v\)。
定理陈述
我们提出一种构造,使得可以设计新型程序。应用此设计的机器 \(M\) 具有以下格式:
- 检索 \(M\) 自身的机器代码 \(m\),并将其保存为 \(u\)。
- 使用 \(u\) 及其输入 \(v\) 进行我们指定的任意计算。
所有以此伪代码编写的程序都对应真实的机器,无论是图灵机还是 C++ 程序。
更具体的,在任何良性编码方案下,对于任意双输入机器 \(F[u,v]\)(无论是全机还是偏机),都必然存在一台单输入机器 \(M[v]\),使得对所有的输入 \(v\in\{0,1\}^*\) 都有
其中 \(m = M_\downarrow\),即机器 \(M\) 的代码。
定理证明
-
构造一台全机 \(S\),满足对所有代码 \(u\in\{0,1\}^*\) 及所有输入 \(v\in\{0,1\}^*\):
\[S(u)_\uparrow(v) = F(u,v). \] -
令 \(S\) 扮演变换器的角色,应用 Kleene 递归定理可得存在不动点
\[m \cong S(m). \]此时令 \(M = m_\uparrow\),则对任意输入 \(v\) 有
\[M(v) = S(m)_\uparrow(v) = F(m,v). \]
关键仍在于如何构造全机 \(S\)。为了保证 \(S\) 为全机,\(S\) 不能直接运行 \(F\) 的任何部分。相反,在输入 \(u\) 时,\(S\) 构造一个机器 \(Q\) 的描述,使其行为如下:
- 对任意输入 \(v\),模拟 \(F\) 在输入 \(u,v\) 上运行,并输出相同的结果 \(F(u,v)\)。
然后,\(S\) 输出 \(q\),即机器 \(Q\) 的代码。根据 Kleene 递归定理,存在 \(m\) 使得 \(m\cong S(m)\),因此 \(M=m_\uparrow\) 确实能够检索自身的代码 \(m\) 并按预期执行后面的计算。
一个类比
可以将此设计类比于 C++ 中的 函数。存在一个(非 C++ 编译器)编译器,它以使用函数的 C++ 程序为输入,输出一个在功能上可证等价且不含函数的程序。(当然,该编译器通过操作程序的表示来工作,而不是去拆解黑箱内部。)这意味着引入函数并不扩展我们的计算能力。
然而,在程序中使用函数,使我们更容易表达思想;将程序划分为若干过程与子过程,符合人类认知习惯。函数 可以 被消除,并不意味着它们 应该 被消除;正相反,正是因为它们 可以 被消除,我们才有信心在计算的概念中接纳它们。
同样的,引入这种自指设计也不扩展我们的计算能力。但它开启了一种新的表达方式:能够书写「了解自身,自我反思并相应的改变行为」的程序。它让我们以机器本身的「声音」去编写,而不仅仅是数学家的视角。正是自指可以被「抹去」的可能性,证明了它的合法性 —— 这不是一种放纵,而是对理论所允许之事、机器本已能为之事的真实反映。

浙公网安备 33010602011771号