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 \cong T(y). \]

换言之,\(y\)(作为代码)在功能等价意义下,是变换 \(T\) 的不动点。

定理证明

  1. 构造一台全机 \(D\)(称作 对角机),满足对所有代码 \(a\in\{0,1\}^*\)

    \[D(a) \cong A(a). \]

  2. 定义全机

    \[X = T \circ D, \]

    \[X(a) = T\bigl(D(a)\bigr), \]

    是一种函数复合。

  3. \(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\) 的描述:

  1. 解码 \(a\) 以还原机器 \(A\)
  2. 模拟 \(A\) 在输入 \(a\) 上运行,得到代码 \(b = A(a)\)
  3. 解码 \(b\) 以还原机器 \(B\)
  4. 对任意输入 \(w\),模拟 \(B\)\(w\) 上运行,并输出相同的结果 \(B(w)\)

最终,\(D\) 输出 \(q\),即 \(Q\) 的代码。此构造保证 \(D\) 在每个 \(a\) 上均能停机,故 \(D\) 为全机。由于 \(D\) 并未真正运行 \(A\)\(B\)\(Q\) 的任何主体计算,仅仅是写出 \(Q\) 的代码(该代码包含两个解码与两个模拟模块),故保证了全性。

由此,\(Q\)\(A(a)_\uparrow\)​ 在所有输入上表现一致,遂得

\[D(a) \cong A(a). \]

在整个证明中,这一对角机的构造是唯一真正「落脚于」计算模型本身的部分。除了编码与解码之外,该构造还要求模型能模拟任意机器在任意输入上的运行,而这正是赋予计算以强大力量的核心能力。

自指程序 —— 获取自身机器代码

在本节中,当我们说 \(M[v]\) 是一台单输入机器,意思是 \(M\) 是一台单输入机器,该输入称为 \(v\)。类似的,当我们说 \(F[u,v]\) 是一台双输入机器,意思是 \(F\) 是一台双输入机器,这两个输入称为 \(u, v\)

定理陈述

我们提出一种构造,使得可以设计新型程序。应用此设计的机器 \(M\) 具有以下格式:

  1. 检索 \(M\) 自身的机器代码 \(m\),并将其保存为 \(u\)
  2. 使用 \(u\) 及其输入 \(v\) 进行我们指定的任意计算。

所有以此伪代码编写的程序都对应真实的机器,无论是图灵机还是 C++ 程序。

更具体的,在任何良性编码方案下,对于任意双输入机器 \(F[u,v]\)(无论是全机还是偏机),都必然存在一台单输入机器 \(M[v]\),使得对所有的输入 \(v\in\{0,1\}^*\) 都有

\[M(v) = F(m,v) \]

其中 \(m = M_\downarrow\),即机器 \(M\) 的代码。

定理证明

  1. 构造一台全机 \(S\),满足对所有代码 \(u\in\{0,1\}^*\) 及所有输入 \(v\in\{0,1\}^*\)

    \[S(u)_\uparrow(v) = F(u,v). \]

  2. \(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\) 的描述,使其行为如下:

  1. 对任意输入 \(v\),模拟 \(F\) 在输入 \(u,v\) 上运行,并输出相同的结果 \(F(u,v)\)

然后,\(S\) 输出 \(q\),即机器 \(Q\) 的代码。根据 Kleene 递归定理,存在 \(m\) 使得 \(m\cong S(m)\),因此 \(M=m_\uparrow\) 确实能够检索自身的代码 \(m\) 并按预期执行后面的计算。

一个类比

可以将此设计类比于 C++ 中的 函数。存在一个(非 C++ 编译器)编译器,它以使用函数的 C++ 程序为输入,输出一个在功能上可证等价且不含函数的程序。(当然,该编译器通过操作程序的表示来工作,而不是去拆解黑箱内部。)这意味着引入函数并不扩展我们的计算能力。

然而,在程序中使用函数,使我们更容易表达思想;将程序划分为若干过程与子过程,符合人类认知习惯。函数 可以 被消除,并不意味着它们 应该 被消除;正相反,正是因为它们 可以 被消除,我们才有信心在计算的概念中接纳它们。

同样的,引入这种自指设计也不扩展我们的计算能力。但它开启了一种新的表达方式:能够书写「了解自身,自我反思并相应的改变行为」的程序。它让我们以机器本身的「声音」去编写,而不仅仅是数学家的视角。正是自指可以被「抹去」的可能性,证明了它的合法性 —— 这不是一种放纵,而是对理论所允许之事、机器本已能为之事的真实反映。

posted @ 2025-04-19 22:01  叶语星辰  阅读(209)  评论(0)    收藏  举报