计算的极限(续)

计算的极限》系列                                                                                                                                                     by 

矛盾的自我指涉

在现实中,证明某种东西不存在是非常困难的。要证明某种东西存在,只要举出一个例子就可以了;但要证明某种东西不存在,就要想办法排除所有的可能性,而在现实生活中,这几乎是不可能的。所以,只要能排除那些比较主要的可能性,任务就算完成。但在数学中,情况大不相同:通过形式逻辑的方法,我们可以确实地证明某种数学对象不存在。这都要归功于数学那彻底的抽象化和形式化。

数学家在证明某个数学对象不存在的时候,经常会来一招“欲擒故纵”:首先假设它存在,那么它必然具有某些特定的性质,再利用这些性质,用严密的逻辑推理引出一个不可能的结论。既然结论是不可能的,而逻辑推理又没有问题,那么一定是推理的出发点出了差错:作为推理基础的那个东西,其实并不存在。这种证明方法,就是反证法。

现在,我们尝试用反证法证明停机问题是不可计算的。

按照反证法的格式,我们先反其道而行之,假设停机问题是可以计算的。根据定义,这说明存在一台图灵机P,使得向它输入某个图灵机M的状态转移表编码,以及初始输入I,图灵机P就能在有限步运算内,判断出机器M在输入I上是否会停止。

接下来,我们将要用图灵机P构造一个逻辑上不可能存在的结构,这将是证明的关键。

我们来考虑一个新的图灵机R,它的输入是某个图灵机M的状态转移表编码<M>。图灵机R先“调用”图灵机P,判断图灵机M在初始输入<M>上是否会停止。用现代的计算机语言来说,就相当于调用函数P(<M>,<M>)。如果图灵机P得出的结论是机器M在输入<M>上会停止的话,图灵机R接下来就会进入死循环;否则,如果机器M在输入<M>上不会停机的话,图灵机R就停止。

图灵机R的构造有两个奇怪之处。

首先,在图灵机R的运作中,它尝试判断一台图灵机M在它自身的编码<M>上的运作情况。此时,图灵机M不仅是程序,同时也是数据。这提醒我们,其实程序和数据没有实质的区别。程序只是一种特殊的数据,能够被分析、整理、改写。

事实上,我们每天都在使用处理程序的程序。比如说杀毒软件,其实就是一种扫描程序的程序。它检查每个程序的内容,判断程序中有没有威胁计算机安全的恶意代码。用杀毒软件扫描它自身,实际上就是让这个程序运作在它自身的代码之上。我们也可以用记事本打开记事本的程序本身,或者用压缩软件打一个包含它程序本身的压缩包。这些例子都说明了一个道理:程序就是一种数据。正因为程序就是数据,我们才得以完成图灵机的自我指涉。

其次,在图灵机R的构造中,如果M在输入<M>上停机,那么R就不停机;如果M在输入<M>上不停机,那么R就停机。这就是说谎者悖论的翻版:它的行为要与自己的判断相悖。

这样,我们就凑齐了说谎者悖论的两个要素:自我指涉和自我否定。剩下的,就是如何将这两个要素组合在一起,引出不可调和的矛盾了。

为了引出矛盾,我们来考虑图灵机R在自己的编码<R>上的运行情况。

如果R在<R>上停机的话,R必定没有进入死循环。所以,在调用图灵机P时,得到的必然是“图灵机R在输入<R>上不会停机”,才能避免死循环。但图灵机P的这个结论不符合我们的假设,出现了逻辑矛盾,所以R不可能在<R>上停机。

如果R在<R>上不停机的话,因为图灵机P必定在有限时间内完成计算,所以R必定进入了死循环。而R进入死循环的先决条件是,在调用图灵机P时,得到的是“图灵机R在输入<R>上停机”。而图灵机P的这个结论,同样不符合我们的假设。由于同样的逻辑矛盾,R同样不可能在<R>上不停机。

所以,根据严密的逻辑,我们构造的图灵机R在自己的编码<R>上,既不可能停机又不可能不停机,这是不可能的。另一方面,我们的逻辑推理也是没有问题的。尽管多么不情愿,剩下的可能性只有一种:我们假设的那个能完美解决停机问题的图灵机P,根本不存在!也就是说,停机问题是不可计算的。

这个结论,我们称之为停机定理。以上的论述,作为停机定理的证明远远不算严谨,还有很多细枝末节需要填充。但这些细节都是技术性的,并不妨碍主要的思想:矛盾的自我指涉。

停机定理的证明,一如哥德尔不完备性定理的证明,核心是化了妆的说谎者悖论。图灵机的能力如此强大,一台通用图灵机就可以完成一切图灵机的工作,将所有图灵机作为数据处理。也正因如此,图灵机不能解决某些牵涉它自身的问题,否则总会存在一些自我否定的“说谎者”,利用能解决牵涉自身问题的那些图灵机,完成被逻辑所禁止的,致命的自我指涉。图灵机的能力,在必然的逻辑推演下,同时也成了它的枷锁。

不可判定的重复

实际上,图灵一开始并没有证明停机定理。他证明的是:不存在这样的程序,能判断任意图灵机是否会至少打印出一个1。这里的“1”可以换成任意的符号。这个证明的方法要稍复杂些,不过本质上仍然是通过自我否定与自我指涉来制造悖论。而事实上,许多(但不是所有)有关图灵机的问题,都能用同样的方法被证明是不可计算的。这样,图灵手上就握有一套不可计算的问题,可以开始进攻希尔伯特的问题。

我们回顾一下希尔伯特的问题。哥德尔证明了,所谓的“一阶谓词演算”是完备的。也就是说,在这个数学系统中,每个真理都能被证明,“真”和“能被证明”这两个概念是一致的。希尔伯特的可判定性问题是:是否存在一种计算过程,可以在有限步运算内,判断在这个完备的数学系统中每个命题的真假?

一阶谓词演算作为数学系统,在能力上实在是比不上数学家们常用的逻辑系统:它连自然数都不能很好地定义。但图灵发现,这个稍弱的数学系统已经足以表达图灵机的运行过程。对于每个图灵机M,通过巧妙然而机械化的操作,图灵都能构造出一阶谓词演算中的一个命题U(M),使得U(M)成立当且仅当图灵机M会至少打印出一个1!也就是说,命题U(M)是否为真与图灵机M的运行过程息息相关。

剩下的证明就如同探囊取物了。如果希尔伯特的可判定性问题是可以计算的话,必定存在一台图灵机H,可以在有限时间内,判断每个命题的真假。对于一台图灵机M,我们要知道它是否会至少打印出一个1,可以先机械化地计算出与M有关的命题U(M),然后用图灵机H去判断U(M)的真假,从而判断图灵机M是否会至少打印出一个1。也就是说,利用图灵机H,我们可以用计算回答一个不可计算的问题,而这是不可能的。所以,图灵机H并不存在,希尔伯特的可判定性问题的答案只有三个字:不可能。

希尔伯特的期望,又一次化为泡影。逻辑弄人。

图灵确信自己解决了希尔伯特的判定问题后,很快将他的想法写成了论文,它的题目是:

论可计算数,及其在可判定性问题上的应用》(On Computable Numbers, With an Application to the Entscheidungsproblem)

他将论文交给了数理逻辑课的纽曼教授。这篇论文在纽曼教授的桌上放了几个星期。当教授终于有时间细读图灵的论文时,一开始根本不敢相信希尔伯特的问题竟然能通过对如此简单的机器的论证而解决,但无懈可击的逻辑论证最终战胜了怀疑。这无疑是划时代的工作,最终埋葬了希尔伯特的宏伟计划。

但正当纽曼教授联系各方,想办法发表图灵的论文时,从大西洋彼岸的普林斯顿,寄来了一篇论文:

初等数论中的一个不可解问题》(An unsolvable problem of elementary number theory)

它的作者是丘奇(Alonzo Church),普林斯顿大学的一位年轻数学教授,当时在数理逻辑这一领域已经小有名气。而这篇文章的最后一句话是:

In particular, if the system of Principia Mathematica be ω-consistent, its Entscheidungsproblem is unsolvable.
(特别地,如果《数学原理》中的系统是ω-一致的话,它的可判定性问题是不可解的。)

对于图灵来说,这绝对不是一个好消息,因为这正是他的结果。

那么,丘奇又是如何得到这个结论的呢?


函数构成的世界

【图片来自Wikipedia】

丘奇作为图灵在数学上的前辈,思考的问题自然比图灵要深远得多。图灵考虑的问题,仅仅是希尔伯特的可判定性问题,而丘奇当时思考的,是如何重构数学的基础。

当时正是第三次数学危机勃发之际,数学界各路人马对数学基础应该置于何处争论不休。当时公理化集合论刚刚建立,作为新事物,自然有人持观望态度,而丘奇就是其中一位,他觉得自己可以创造一个更好的理论,以此作为数学的基础。与其选择集合与包含这两个概念,他选择了数学中另一个重要的概念:函数。

数学家眼中的函数,比你想像的要广泛得多。在中学数学中,说到函数,自然会联想起它在平面直角坐标系的图像。这是因为中学数学中的函数,大部分情况下不过是从实数到实数的映射而已。而数学家眼中的函数,可能与程序员眼中的函数更相似:它们更像是一个黑箱,从一边扔进去某个东西,另一边就会吐出来另一个东西。

【感谢neko(@iNEKO_mini)提供图片】

我们并没有限定能扔进黑箱的东西。事实上,将黑箱本身扔进黑箱也是可以的。对这种把戏,数学家们再熟悉不过了,在泛函分析这一数学分支中,数学家们就经常研究一种叫“算子”的数学概念,在某些特殊情况下,就是那些将一个函数变成另一个函数的函数。所以,不去限定能扔进黑箱的东西,似乎也没什么问题。

【再次感谢neko(@iNEKO_mini)提供图片】

总而言之,函数就是将一个函数变成另一个函数的东西。那么,要用符号表达这么普遍的函数概念,我们需要什么呢?

首先,就像在方程中我们用x, y, z等等符号表示未知数,我们也希望能用符号表示未知函数。我们将这些表示未知函数的符号称为变量。

其次,如果我们手上有两个函数 M N ,那么我们自然希望研究函数 N 被函数 M “处理”之后会变成什么。也就是说,既然 M 是一个函数,能将一个函数变成另一个函数,那么 M 会将 N 这个函数变成什么呢?即使不知道具体的过程,我们也希望能表达最后的结果。所以,我们将 N M 处理后得到的结果记为 (MN) 。这被称为函数的应用(application)。

最后,也是最抽象的概念,就是函数的抽象(abstraction)。

我们可以用变量x来表示未知的函数,自然也可以用x来构造更多的函数。比如 (xx) 就表示函数x应用到自己身上的结果,而 ((xx)y) 就表示将刚才得到的结果应用到另一个未知函数y上得到的结果,如此等等,不一而足。如果我们将变量x替换成一个具体的函数f,那么这些包含变量x的表达式就会变成包含具体函数f的表达式。

也就是说,如果我们有一个包含变量x的表达式 M ,对于任意一个函数f,我们可以将它对应到一个新的表达式,记作 M(f) ,而这个新的表达式是将M中的所有x替换成f所得到的。比如说,如果 M=(xx) ,那么 M(f)=(ff) M((yy))=((yy)(yy)) ,等等。

一个表达式也是一个函数。我们从表达式M出发,可以得到把一个函数f对应到另一个函数 M(f) 的方法,而这正是一个函数。也就是说,我们能从一个表达式“抽象”出一个函数。我们将这个函数记作 λx.M ,x是我们所要考虑的自由变量。

【注:在这里,我们只能替换那些所谓的“自由变量”。粗略地说,自由变量是那些之前没有被抽象过的变量。详细的技术细节略显繁复,而且也有办法绕过,所以于此略过。】

从这三点基本要求出发,丘奇开始定义他的λ演算。他将他考虑的这些函数称为“λ项”,而所有的λ项都可以从以下三种途径构造而成:

1. (变量)所有变量 x,y,z, 都是λ项。
2. (应用)如果 M N 都是λ项,那么 (MN) 也是λ项。
3. (抽象)如果 M 是一个λ项而x是一个变元,那么 λx.M 也是一个λ项。

接下来,丘奇定义了一些λ项之间的转换法则。

首先是 α 重命名,这项转换可以使我们在遵循一定的规则下,任意变换λ项中的变量名称,而不改变λ项本身的意义。也就是说,λ项中变量的名称无关紧要,无论是x, y, z还是苹果、香蕉、榴莲,又或者是小庄、游游、李清晨,项的本质是不变的。

然后是最重要的 β 规约:

(λx.M)fβM[xf]

在这里, M[xf] 实际上是 M(f) 的严谨记法,表明了我们要替换的变量。而 β 规约实际上就是根据定义计算函数的一个过程。

最后,还有一个 η 变换。它的实质是所谓的外延性,也就是说,如果两个项对所有函数的作用都是相同的话,那么就认为这两个项是相同的。

这么几条简单的法则,就是丘奇的λ演算的全部内容。

那么简单的法则,很难想象λ演算能表达什么复杂的数学概念,这看起来不过是符号的简单推演而已。但既然同样简单的图灵机中也暗藏玄机,那说不定λ演算也有它自己的复杂内涵。

【图片来自http://blogs.msdn.com/b/ashleyf】

分崩离析的世界

丘奇最初建立λ演算的目的,是希望将它作为一种逻辑推理的方法。我们可以将某些逻辑公理表达为λ项;对于某个逻辑命题,我们可以先将其转化为λ项,再根据λ演算的法则将它不断简化,而命题正确与否就蕴含在计算结果之中。

通过这种方法,丘奇成功地在λ演算的框架下表达了不少的数学系统。λ演算看起来是如此的成功,甚至达到了无所不能的程度。

但如果我们还记得哥德尔的教训的话,无所不能有时并不一定是什么好事,因为在数学和逻辑的领域中,对于有意义的逻辑系统,强大的表达能力必然伴随着坚不可摧的限制。如果一个系统无所不能,那么更大的可能是它本身就自相矛盾。就像一个理论,如果对的也好错的也罢,正面反面都能解释得通,那相当于完全没有解释。

果然,几乎在丘奇向学术界展示他的λ演算的同时,克林(Kleene)和科里(Curry)就证明了,作为一个逻辑推理系统,λ演算在本质上就存在着矛盾,它是不一致的。通过适当地构造一些λ项,克林和罗瑟(Rosser)成功地利用λ演算找到了一切命题的证明,甚至包括那些错误的命题!一个连错误的命题都能证明的逻辑系统,也就是说一个不一致的逻辑系统,没有任何意义。

值得一提的是,上面这几位后来都成为了数理逻辑界的大人物。克林和罗瑟是丘奇的学生,而科里则师从希尔伯特。我们后面还会讲到这位科里教授,他的事迹之一就是有整整三个不同的编程语言是以他的名字命名的,连中间名都用上了,影响力可见一斑。

事实上,丘奇当初在筑建λ演算之时,就已模糊地认识到了这个问题,但他觉得这只是一种幻象,通过某些适当的限制,就能摆脱这些恼人的问题。但丘奇错了,实际上这是一个本质性的问题。

那么,问题的根源在何处?

我想,读过本系列之前文章的读者应该都猜到了,又是那绕不过去的自我指涉!

自指

但是,自我指涉在什么地方呢?

还记得λ项是什么东西呢?它的原型是函数,但不是一般的函数。在定义λ项之时,我们允许它将任意的λ项转化为另一个λ项。既然是任意的λ项,那么当然也包括它自己。如果将λ项看成程序的话,那又是一个可以将自己当作输入数据的程序。与图灵机不同的是,在λ演算之中,根本没有数据和程序之分,一切都是λ项,它们既是程序,也是数据。

λ演算的能力不止于此。考虑所谓的Y组合子:

Y=λx.(λy.x(yy))(λy.x(yy))

将它应用到任意一个函数$f$时,我们会得到:

Yf=(λx.(λy.x(yy))(λy.x(yy)))f
β(λy.f(yy))(λy.f(yy))
βf((λy.f(yy))(λy.f(yy)))
=f(Yf)

这样不停替换下去,得到的结果就相当于将函数$f$应用到自身任意多次。λ演算的能力,特别是在自我指涉方面,于此可见一斑。

正是如此强大的表达能力,使得作为逻辑系统的λ演算一无所能。如果还记得图灵机是怎么在停机问题上失效的话,实际上利用相似的技巧,对于任意的命题,我们可以构造一个λ演算中的证明,无论命题本身是对是错。

后来Curry的工作在更深刻的意义上揭示了这一点。他概括了λ演算的某些特性,然后证明了对于无论什么逻辑系统,只要它拥有这些特性,那么它必然是不一致的。而这些特性,也恰好是会碍事的那部分自我指涉所需要的。

于是,作为一个逻辑模型,λ演算一败涂地。

但丘奇没有就此止步。虽然λ演算不能如他所愿成为数学的推理基础,作为一个计算模型似乎倒也不错。我们可以将一个计算过程看成函数,将输入数据转化为输出数据的函数。于是丘奇将“可有效计算”定义为“可以用λ演算表达的函数”。这时,自我指涉的特性就成为了不可多得的优点,因为这实际上说明λ演算有强大的计算能力。利用自我指涉的特性,通过相似的构造方法,丘奇同样解决了希尔伯特的可判定性问题,得到了与图灵相同的结论。

丘奇在构想λ演算之时,瞄准的是更为基本的数学基础模型,但它却成为了可计算性的模型,真可谓“无心插柳柳成荫”。这就是图灵看到的那篇论文的由来。

不难想象图灵当时读到这篇论文时的心情。如果将数学比作攀山,当你千辛万苦登上一座处女峰,却蓦然发现山顶已经插上了别人的旗帜,你大概会觉得一切都似乎失去了意义。

但数学毕竟不是攀山,不同的路径可能有不同的景致,要论高下为时尚早。况且要比较两者,要先知道两者解决的到底是不是同一个问题。虽然图灵和丘奇解决的都是同一个问题,但他们对“可计算性”各自做了不同的假定。图灵认为“可计算的问题”就是图灵机可以解决的问题,而丘奇则认为那应该是λ演算可以解决的问题。

问题是,图灵机和λ演算这两个计算模型,它们解决问题的能力一样吗?两种视点下的可计算性,到底是殊途同归,还是貌合神离?


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值