6 分钟阅读

写给程序员的哥德尔第一不完备性定理

哥德尔的不完备性定理被誉为「20 世纪最伟大的数学发现」——事实上,这些定理不仅适用于数学,还适用于一切形式系统,并对科学、逻辑、计算机科学、哲学等领域有着深刻的影响。在这篇文章里,我会给出哥德尔第一不完备性定理一个简单但严谨的论证梗概。它的形式化表述是:

任何一致的形式系统 SS,只要其中能够进行「一定量的初等算术」,就是不完备的;也就是说,SS 的语言中存在一些命题,既无法在 SS 中被证明,也无法被证否。

这是一个严谨的证明,重点面向软件工程师和程序员。这篇文章基于我不久前在 YouTube 上找到的一场精彩讲座。我第一次学习如何证明哥德尔不完备性定理,是在一门元逻辑课上,当时涉及各种令人困惑而深奥的话题,比如 Henkin 构造和超穷逻辑(transfinitary logic)。但事实证明,理解哥德尔并不需要这么大费周章!

引言

我们的旅程从定义几样东西开始。首先,我们把 FF 定义为一个函数,接受一个正整数,返回 01。下面是这样一个 FF 的例子:

isOdd(x)={0x is even1x is oddisOdd(x)=\begin{cases} 0 & x\text{ is even}\\ 1 & x\text{ is odd} \end{cases}

于是,我们看到 isOdd(2)=0isOdd(2)=0,或者 isOdd(38943981)=1isOdd(38943981)=1。我们可以随意定义 FF——只要它的输出要么是 0,要么是 1。设 QQ 为所有这类函数 FF 的集合。

如果存在一个计算机程序(或证明)PP,接受输入 xx 并返回 F(x)F(x),我们就说 FF 是可计算的。不言而喻,PP 必须在有限时间内完成,而且必须正确。那么来看点代码吧。isOdd(x)isOdd(x) 是可计算的吗?

function isOdd(x) {
  return (x % 2);
}

看起来是的!xx 为偶数时这个程序总会返回 0,为奇数时返回 1。再看一个更复杂的例子:

isPrime(x)={0x is not prime1x is primeisPrime(x)=\begin{cases} 0 & x\text{ is not prime}\\ 1 & x\text{ is prime} \end{cases}

isPrimeisPrime 是可计算的吗?

function isPrime(x) {
  if(x < 2) return 0;
  if(x == 2) return 1;
  for(var i = 2; i < x; i++) {
    if(x % i === 0) return 0;
  }
  return 1;
}

多亏了这个 Stack Overflow 回答,看来它确实是可计算的。设 AAQQ 中所有可计算函数的集合。我们(费了些力气)刚刚发现 isOddisOddisPrimeisPrime 都在 AA 里——也就是说,它们是可计算的。

但真正的大问题来了:QQ 中所有函数都是可计算的吗?或者等价地说:

A=?QA\stackrel{?}{=}Q

如果 A=QA=Q,那哥德尔就错了,所以我们需要想出一个巧妙的办法来证明 AQA\subset Q。换句话说,我们需要证明,存在一些函数,以正整数 xx 为输入、返回 01,却是我们根本无法实现的。

铺垫

那么,怎么证明某些函数 FF 是不可计算的呢?好吧,我们用最朴素的办法来做。既然我们已经在用 JavaScript,那就干脆把每一个 JavaScript 程序统统打印出来。是的,所有的程序。为了方便,我们可以按字母顺序和长度(即字典序)排列它们。为了更省事,我们可以直接扔掉那些会无限循环、或者不返回 10 的程序。等一切尘埃落定,我们剩下的是无穷多个程序,它们大概是这样开头的:

function F1(x) {
  return 0;
}
function F2(x) {
  return 1;
}

再往后……

function Fn(x) {
  return 1 - 1;
}

再往后……

function Fn(x) {
  return x / x;
}

再往后……

function Fn(x) {
  return (x % 2); // hey, this is the isOdd function from before!
}

再更往后……

function Fn(x) {
  return (x % 2) / 1;
}

再更往后……

function Fn(x) {
  let someRandomVariable = x ^ x;
  let abcd = someRandomVariable / y;
  if (abdc > -12) return 0;
  return 1;
}

你大概明白了。现在我们有了每一个用 JavaScript 写出、输出 01 的可能程序。换句话说,我们刚刚填满了 AA:现在每一个可计算函数都对应着一个程序。让我们把它们放进一张叫 TT 的大表里。列标题表示输入(正整数),行表示可计算函数(以及隐含的、实现它们的程序)。

1234567n
f1f_1f1(1)f_1(1)f1(2)f_1(2)f1(3)f_1(3)f1(4)f_1(4)f1(5)f_1(5)f1(6)f_1(6)f1(7)f_1(7)f1(n)f_1(n)
f2f_2f2(1)f_2(1)f2(2)f_2(2)f2(3)f_2(3)f2(4)f_2(4)f2(5)f_2(5)f2(6)f_2(6)f2(7)f_2(7)f2(n)f_2(n)
fof_{o}1010101fo(n)f_{o}(n)
fpf_{p}0110101fp(n)f_{p}(n)
fif_ifi(1)f_i(1)fi(2)f_i(2)fi(3)f_i(3)fi(4)f_i(4)fi(5)f_i(5)fi(6)f_i(6)fi(7)f_i(7)fi(n)f_i(n)

你会注意到 isOddisOddisPrimeisPrime 也进了我们的表(分别作为 fo 和 fp)。到目前为止一切顺利。看起来我们想到了所有情况。但让我们定义一个新函数:

fˉ(i)=1fi(i)\bar{f}(i)=1-f_i(i)

其中 fif_i 是表 TT 中的第 ii 个函数。首先,让我们确认一下 fˉ\bar{f} 是良构的。输入 ii 是一个整数。既然 fif_iTT 中有对应的一行,fi(i)f_{i}(i) 就会返回 01。最后,10=11-0=111=01-1=0,所以两种情况都是良构的。

fˉ(i)=1fi(i)will return 0 or 1will return 0 or 1\bar{f}(i)=\stackrel{\text{will return }0\text{ or }1}{\overbrace{1-\underset{\text{will return 0 or 1}}{\underbrace{f_{i}(i)}}}}

因此,fˉ\bar{f}QQ 里,但它在 TT 里吗?

证明

要看出 fˉ\bar{f} 为何不可能在 TT 里,其实相当直接。假设 fˉ\bar{f} 就是 f2f_2。正如我们目前所见,f2(2)f_2(2) 要么是 0,要么是 1。但如果 f2(2)=0f_2(2)=0,那么 fˉ(2)=1f2(2)=10=1\bar{f}(2)=1−f_2(2)=1−0=1。于是我们有:

f2(2)fˉ(2)f_2(2) \neq \bar{f}(2)

糟糕。好吧,那它不是 f2f_2。那样也太容易了。那 f421f_{421} 呢?如果 f421(421)=1f_{421}(421)=1,那么 fˉ(421)=1f421(421)=11=0\bar{f}(421)=1−f_{421}(421)=1−1=0。于是我们有:

f421(421)fˉ(421)f_{421}(421) \neq \bar{f}(421)

事实证明,无论我们从 TT(以及隐含的 AA)中挑出哪个可计算函数,它都会在至少一个输出上与 fˉ\bar{f} 不一致。因此,我们得到了一个惊人的结论:

AQA\subset Q

结语

换句话说,存在某些东西,比如 fˉ\bar{f},是我们在形式系统中既无法证明也无法证否的。鉴于本文一直在用 JavaScript,一个程序无法在 TT 中引用它自己的字典序索引,这也就说得通了。话虽如此,你可能会忍不住想,或许有办法「绕开」这个限制;只要你的语言足够聪明,也许就行。我以后会写一写哥德尔第二不完备性定理,它会给这口棺材钉上最后一颗钉子:根本无法绕开。