程序与证明
程序与证明
来源: <http://www.tuicool.com/articles/YRZvem>
{-
Turing complete, or not Turing complete: that is the question:
Whether 'tis nobler in the machine to suffer
The undecidability of a paradoxical recursion,
Or to take proofs against a sea of calculi,
And by opposing halt them?
-- soimort's soliloquy
-}
- §0 前言
- §1 从计算到程序
- §2 语言的巴别塔
- §3 程序即证明
- §4 程序非证明
- §5 结语
§0 前言:关于一部传记文学,以及未来的计划
大约七个多月以前,我在 Blogger 上的随笔里设想过一个宏大的计划:用七个月的时间,写作一部从亚里士多德时代的传统逻辑延续到今天的 数学基础 研究、从若干次数学危机中发展起来的公理化系统到现代科学的形式化、从欧氏原本中最初以几何形式为载体的数学证明到借力于现代计算机工具的 Q.E.D. 宣言 和 Univalent Foundations 工程 ,横跨数学、逻辑学、形式语言学和程序语言等诸多领域的计算科学传记史(从尚未完成的第一章来看,这似乎更接近于一部野史,灵感大概来源于很久之前看过的一个流传颇广的讲各种数学家八卦的帖子《 Heroes in my heart 》——但绝对不再是什么伪史)。当时我这样写道:
我想在这个系列的文章里探讨一个最基本的问题:什么是程序。最初,我为它所设想的标题叫“程序语言是什么”,或“一个没有图灵机的世界”,阐释图灵机的计算模型为何适用于描述我们的现实世界;为何又不适合于描述逻辑的世界;阐释为何 Coq、Agda 这样的函数式语言从设计上与图灵构想的方式背道而驰,甚至并非是图灵完全的,却仍可以称之为程序语言,而且更加接近我理想中纯粹的语言;阐述证明论与类型论之间的关系,为何从形式化逻辑出发方能构建出可验证的终极程序或软件工程。最后我发现,这已经不仅仅是程序语言的本质问题了;它实则是关乎计算本质的终极问题。
七个月之后回过头再看,这项计划果然不出所料地搁浅了。但我还是想最后写一些关于程序、计算和证明的内容——它既非完全的科普,也算不得纯文学体裁的散文;只是为了弥补自己从未写过有关这方面的文章的缺憾,也作为这个博客的一个收尾(可能)。
对于我自己来说,这只是一切的开始之开始。在任何一个领域,学习到的越多,越是感到自己认知的肤浅和局限,而原地踏步时唯有陷入自我膨胀的恶循环;无论是曾经无从所求选择的地球物理学也罢,心猿意马的系统生物学也罢,还是这三年亡羊补牢的计算机编程也罢。我以为,人类的知识体系累积到今日,计算科学的基石已同其他学科领域别无二致,亦绝非匹夫之力可以撼动。在这个没有英雄的时代,即便是天才也无法不通过努力学习而获得成果,更何况我这样智商尚处中游的普通人。只有傻子和民科才会画地为牢,把自己的认知局限想当然地看作整个宇宙的中心,还自以为发现了一切事物的终极真理;而现实的知识如同芝诺的圆圈,有的人知道得少一些,有的人知道得多一些,更有人在努力开拓圆圈的边界——然而圆圈以外的区域,对于任何人而言却是无限未知。(我想,这大概就是 Ph.D. 的意义罢:“The world looks different to you now. Don't forget the bigger picture. Keep pushing.”)即使人生是个无解的死循环,现在起步去追求一些有价值的东西,应该还不算太晚。
然而,三年的时间足以改变许多,于我自身。我越来越感受到,网络这种廉价的媒介虽带来了信息的便利,却也充斥着谬误和浮躁,适合于万人追捧的偶像或者小丑,而不再适合知识的沉淀与思考的深度。那种逡巡于图书馆书架间“吹尽狂沙始到金”的喜悦,是再也找不到了。前人尚有“尽信书,则不如无书”的教诲,今天的人却会不假思索地转发和点赞网络上一些或慷慨激昂、或煽情肤浅而毫无逻辑理性可言的文字,实在是一件可笑的事情。这也是我除了写博客之外,越来越无心在别处谈论技术或表达观点的原因之一。知识的快餐化和随之而来的功利化,乃是我们这个时代的悲剧。
而知识的拓荒者并不需要成为摇滚明星,他们中的大部分人总是选择默默无闻,除非在身后因成就而被追随者捧到聚光灯下;因为在生前被普通大众关注和需要,是一件足以让人从自己所投入的事业中分心的事情。黄金也许会被时间的流沙埋没,也许要到数十年甚至百年后才会被人重新发现;来自不同时代的开拓者,也许十之八九会被人遗忘,后人在享受他们含辛茹苦创造的智慧结晶时,不曾记起他们的名字。但对于人类历史的长河来说,这一切都将不足挂齿;那些浮华一时、甚嚣尘上的,纵然一呼百应,终究不过是无足轻重的沙砾而已。倘若我们每个人都因为害怕孤独,害怕自己苦心孤诣淘出黄金又被世人遗忘,而去和时代的烟尘同化、无所作为地度过余生,那么我们什么也不会给后人留下。
在此,且允许我借用多年前对我影响颇深的李兴春的科幻小说《我是龙生第九子》里的一段话:
我是个狂生,我有家学渊源,但我漠视天元术(宋元代数学),讨厌那种市侩的徽商算术(明代商业数学),而偏爱墨家及欧氏几何。我选择了使名理与数理相合的“名数学”,将名实之理,仿作象、数的演算。我知道每一名都有阴阳虚实,而同一名可以只在大、小故(即充分、必要条件)同一,在其余的关系中一阴一阳、一虚一实,由此建立起来的一整套造术算法,是奇特而又美妙的,它使得楚人所卖的无不陷之矛和不可陷之盾可以同世而立,自相矛盾不再矛盾。这是我积十数年心血研究的结果,绝对可靠和完善,它好比我最疼爱的并对其未来前程寄予厚望的独子,今天我将看着它夭折。
“数百年后,必有用我此说者!”我在心里悲怆地喊出,回答我的是无边的暗夜和沉默。
如是,且告诫自己不可为身外之物所左右,无论此生能否学有所成,希望自己可以善始善终。
§1 从计算到程序:图灵完备的神话
……数学,与众多理性科学一样,以一门不严谨的计算技巧之名而诞生。在经历了重重危机之后,人们终于认识到使其公理化的必要性,作为终极的自然科学——物理学理论的基础、一门“元科学”的数学,就这样终于成为建立于形式化逻辑之上的一个严密理论体系。比起现世先验知识的总结,它更加体现出人类智慧的结晶;愈是暴露出人类直觉的模糊与不可靠性(如同非欧几何、ZFC公理体系显示出的与人类感官直觉背道而驰的表象),愈是能彰显出逻辑学的价值。
反对逻辑的人们,必将在招致谬误的表象中迷失了自身,把对事物的认知世俗化成了纯粹人类感官化的思维活动,无法形式化地精确表达,更无法被机器——这种比人脑具有更强大计算力的思维辅助工具无二义性地理解。能够被机器可靠地感知、处理、和转述的那部分,便是有资格被称之为“计算科学”的学科,或者,更加具体化的,“计算机科学”。
——某未完成的计算科学史原稿
如果说可计算性( computability )是计算科学的基本问题,那么图灵( Alan Turing )的图灵机( Turing machine )无疑是第一个被广为接受的通用计算模型。图灵第一次以抽象机器的方式定义了“计算是什么”,这已经足以让他成为计算机科学的先驱了;而和他同时代的邱奇( Alonzo Church )提出了与图灵机计算能力等价的λ演算( λ-calculus ),则更多地被看作是为计算科学奠定基础的逻辑学家。
通过邱奇-图灵论题( Church–Turing thesis ),他们默许了不可判定的停机问题( halting problem )在计算机器里的存在,从而回答了宇宙间一切计算机“终极的计算能力是什么”这个问题,却也因此让计算的机器成为了一个逻辑上不完备的形式系统。 一台完备的具有最大限度可计算能力的计算机器,与一个完备而自恰的逻辑系统实际上是水火不相容的 ;我们在后面还将反复提到这一点。
而反映到具体的计算机中,作为一切程序的灵魂——算法和数据结构表达载体的程序语言,就经常被这样使用“图灵完备( Turing complete )”的限定词来提及,甚至于被 错误地 用作是衡量一种计算机语言 是否程序语言 的标准:
A:FlooP 是一种编程语言吗?
B:是的,因为它是图灵完备的。
A:我可以在 FlooP 语言里实现 X 功能吗?
B:当然可以,它是图灵完备的,理论上你可以用它实现任何功能!
A:BlooP 是一种编程语言吗?
B:不是,因为它不是图灵完备的,它无法实现任意计算。
不,一门语言并不需要与图灵机完全等价的计算能力才能使自己成为程序语言;只要你能够用它来编写程序并让计算机完成某种计算,它就已经够资格称得上程序语言了。事实上,如果你深入地去探究关于 可计算性 的定义,就会发现,所谓图灵完备的程序语言比起非完备语言的唯一理论优势,就是可以让你写出“无论人或机器自身,都无法确定停机与否”的程序。而这种程序的存在,本身就意味着哲学上的悖论和实用范畴内的无意义。关于这点,我将留待稍后再作解释。
在上面的对话中,你可以把假想的 BlooP 和 FlooP 代入任何一种你所熟悉的现实计算机语言名称,从而得出“ 图灵完备就是程序语言,程序语言一定是图灵完备的 ”这句话的确凿证据——比如你大概已经听说过, Brainfuck 语言 是图灵完备的,它总共包含8条指令,可以看作基于命令式的图灵机模型的一个变种;你可能还知道,仅含1条指令的抽象机也可以是图灵完备的,这就是 OISC ; Unlambda 语言 是图灵完备的,因为它的函数式计算模型源自于λ演算,从而使得它的计算能力等价于图灵机;撇开这些纯粹作为理论玩具的 Turing tarpit 不谈,具有最小核心的“实用”语言大概就数各种 Lisp 方言了:Scheme,PicoLisp,Shen,它们无一不是图灵完备的,尽管它们的设计思想与图灵机模型可说是大异其趣;高兴的话,你还可以在 TIOBE 的列表上找到所有被广泛应用于软件业界的主流语言,它们几乎无一例外地总是图灵完备的——这其中甚至包括了一些不能够称之为是“通用语言”的领域特定语言,比如 Transact-SQL ,它是数据库查询语言标准 SQL 的一个扩展超集。当然,最初设计用作排版目的的 TeX和 PostScript 语言,也早已具有了图灵完备性。另外有一条并不新鲜的 新闻 是,有人用 CSS3 编码出了 Rule 110 元胞自动机,如果你学过自动机理论的话,你可能知道它的计算能力等价于图灵机——于是,现在连 HTML+CSS 也都变成图灵完备的了。当然,我们也都承认 HTML 本身并不是一种编程语言,因为至少我在写这篇文章、你在读这篇文章的时候,都只是在用文字传递思想而不是计算。
既然任何一种计算机语言是否图灵完备已经是板上钉钉的事实,理论上说也都可以无一例外地被证明或否证(如果有人愿意做的话),那么,复述这些又有何意义呢?
从定义上来讲,所谓“图灵完备性”,只是用于衡量某 计算模型 的 可计算能力 ,亦即表达计算的能力 的一个标准。换言之,如果一个计算模型具有和图灵机同等的计算能力,那么它就可以称作是图灵完备的。而在谈论“编程语言”这个名词时,我们关注的更多地是它的 可编程能力 ,亦即 表达计算机程序的能力 。一个程序,描述的可以是计算本身,也可以是对计算的模拟,也可以是对计算的模拟之模拟,如此直至无穷。此外,为了使任何一个程序具有实用价值——因为程序最终要在实际的 计算机 上运行,势必要考虑到与外部世界的交互(即,读取和输出数据),又或者是操控状态(即,改变计算机自身状态、或外部世界状态)的能力;而纯粹的 计算 这件事本身,并不涉及数据的输入输出——你可以在纸上用辗转相除法求两个数的最大公约数,在你施行计算这个过程之前,数据就已经在那里了;改变外部世界的状态,对于计算来说也不是必需的——你可以像普通人般心算辗转相除法,也可以像霍金般在脑内推演黑洞的形成,整个过程并不需要纸和笔的介入;甚至连改变计算机器的自身状态,从结果上讲也不是必然的——你可能会在计算过程中记忆一些中间量,但在计算完成之后,你可以选择忘记所有关于该计算的事情:譬如,我无法记起自己曾经求解过哪些一元二次方程,又在我的脑海中留下过什么;我只是知道,计算这件事情曾经发生过许多次。一句话, 纯粹的计算应该是无副作用的 。
而我们知道,编程这件事情一定是充满副作用的。它既需要 与物理世界交互 ,同时又 受到物理世界的制约 。虽然程序语言专家倾向于拿几个简单的原子类型来编码出关于整个宇宙的理论,但现实的机器并不具备无限的运算能力,因此为了足够的执行效率,我们仍需要机器的 ALU 和 FPU 来帮助我们的程序语言执行整数和浮点数这些原始数据类型的运算;需要机器的内存及缓存来提供足够的空间用于存储计算的中间变量。这些为了达成计算而对现实机器所做的妥协,并不在可计算性理论所讨论的范畴内。至于算法和数据结构的设计,时间和空间的优化,计算复杂度的分析,则是人们为了克服实际计算机效率限制所做出的努力——若是宇宙间存在这样的理想计算机,它拥有无限的时钟频率,那么讨论 P 和 NP 这样的 计算复杂性类 将不再有意义。
为了更加直观地感受“ 可计算能力不等于可编程能力 ”这一点,不妨考察一些简单的代码在不同程序语言中的实现方式。这里要举的一个例子便是出了名的“最不实用”的语言之一:Brainfuck。以我去年做过的Google Code Jam 2013 QR 第一题 为例,检查第一行字符串是否为“ OOOO
”,实际上写出来的代码是这样的:(因为读取数据之后做了一些预处理,所以不看 完整代码 的话恐怕很难理解这里的移位方式)
# Row 1
> [-] >> [-] > [ <+ <<+ >>>- ] <<< [ >>>+ <<<- ]
>> ----- ----- ----- ----- ----- ----- --- #O
<< [-] > [-] > [ <<+ >+ >- ] << [ >>+ <<- ] +
> [ <- > [-] ]
< [ >>>
> [-] >> [-] > [ <+ <<+ >>>- ] <<< [ >>>+ <<<- ]
>> ----- ----- ----- ----- ----- ----- --- #O
<< [-