计算机隐藏的那种巨大能量一定会被几个世纪之前的人们看作神迹,看作“真正的魔法”。不过,尽管很多计算机程序都复杂得吓人,但它们全都是由一些基本的步骤组成的,仅凭几个简单的术语我们就能把这些步骤解释清楚。计算机上的运算从不夹杂任何神秘成分,作为思考工具,这正是它的重要价值之一,而且解释这件事儿本身就是一个很有趣的哲学问题。到底计算机是怎样施展它的“魔法”的?能在一个比较基础的层面上理解这个问题非常必要,这一章会提供给我们一些相关启示。
先从一台我们能想到的最简单的计算机——一台寄存器机开始吧,看看它有什么能力以及为什么它会有这样的能力。然后,我们再去考察图灵机和冯·诺依曼机。其实它们也只是一些更高效率的寄存器机而已,笔记本电脑能做的事情寄存器机也都能做,只是你得用它算上几个世纪。之后,我们才能慢慢地搞清楚,更高级的那些计算机“架构”如何在寄存器机这台本体机上增长了速度、壮大了技能,而人类大脑正是其中最重要也是最有趣的一种架构。
等一下,我从没说过“大脑就是一部庞大的计算机”,没有,一次都没有。我想说明的是,如果大脑真的是一部庞大的计算机,那么我们就一定能够找到一种方法将大脑所有的活动都解释清楚,不留下任何神秘色彩。我们采用的这种方法就是逆向工程:探究一个复杂的系统,分析它能做什么,是怎样做到的。逆向工程让我们知道了,心脏是如何像一个泵那样履行自己的职责,肺又是怎样收集氧气排放二氧化碳的。神经科学是逆向工程应用于大脑研究的一种尝试。我们都知道大脑能做什么:预测、支配、记忆、学习,接下来还需要我们搞清楚的是,大脑是怎样做到这些的。
这是个充满争议的话题。2000年,小说家汤姆·沃尔夫(Tom Wolfe)曾在自己的文章《抱歉,你的灵魂已死》(Sorry, But Your Soul Just Died)中详细描述过这一敏感话题,随即便引起了一场激烈的论辩。如果我们不想把时间都浪费在那些雄辩和谴责上,而是要真正探索这个艰险的领域,我们还需要一些更锐利的思考工具。我们的大脑是否真的具有并利用了计算机永远都无法企及的那些神奇能力呢?要想有根据地解决这个问题,我们首先得知道计算机都能做些什么,它们是怎么做到的。而要想圆满地证明“我们的大脑不是,也不会是一台计算机”,可用的方法是:(1)证明大脑某些“活动部件”所参与的信息处理工作是计算机无法模仿的;或者(2)证明我们平日熟悉和喜爱的那些精神壮举并不能由组合、汇总计算机部件的简单工作或通过任何计算机形式的处理方式来实现。
除了哲学家外,还有神经科学家、心理学家、语言学家,甚至物理学家都认为这种人类大脑的“计算机隐喻”极具误导性,因为大脑显然能够做很多计算机不能做的事情。他们通常(但也不绝对)先对“计算机是什么”或者“应该是什么”这个问题规划出一个很天真的预设,最后就得出了这个明显但又不相干的命题,即大脑可以做很多笔记本电脑不能做的事情,因为笔记本电脑没有充足的传感器和效应器,内存低,运行速度也有限。而事实上,关于“通常情况下计算机能做什么”这个问题,如果真要回答,我想我们需要弄清楚的应该是:在通常情况下,计算机的能力来自哪里,是怎样得以发挥的?
1957年,那还是计算机时代的初期,王浩发明了寄存器机这个神奇的东西。他是逻辑学家,是库尔特·哥德尔的学生,顺便提一句,他也是一位哲学家。寄存器机是一件简洁的思考工具,每个人的工具箱里都该有这么一件,可惜它至今还仍未被大家所熟知。(37)寄存器机是理想化的、假想出的计算机,它只是由有限数量的寄存器和一个处理单元构成的,具有十足完美的合理性。
寄存器是一些存储单元,每个寄存器都有自己各自的地址,比如寄存器1、寄存器2、寄存器3,以此类推,它的内容包含一个整数,如0、1、2、3,……。寄存器就像是一个一个的大盒子,里面可以放置任意多的(从0到无限多个)豆子。不管这些盒子有多大,而我们总是希望这些盒子能无穷大,因为那样它们才能装上无限大的整数。但我们对大盒子的这些设定是有目的的。
处理单元只具备三种简单的能力:它只能“遵循”三种“指令”,一次执行一条。每一串指令构成的序列都是一个程序,而每一条指令由一个数字来标记,即给每条指令分配一个ID号。这三条指令分别是:
结束。表示将自己停止或者关闭。
增量。将寄存器n中的数字加1,或者说在盒子n中再填入一粒豆子,然后进入下一步,步骤m。
减量。将寄存器n中的数字减1,或者说从盒子n中拿走一粒豆子,然后进入下一步,步骤m。
减量指令与增量指令的运行原理基本相同,只是减量指令中多了一个非常重要的难点:如果寄存器n中的数字是0该怎么办?我们已经无法再从中减1,因为寄存器中不能持有负的整数,正如你无法从一个空盒子中再拿走一粒豆子,面临如此窘境只得另寻它路。办法是:跳转到其他地方运行下一步指令。每一个减量指令都需要在程序中列出那个接受跳转的位置,以应对当前寄存器数字为0的情况。所以,减量的完整定义应该是:
减量。将寄存器n中的数字减1,如果可以执行则继续进行步骤m,如果寄存器n不能减1,则跳转至步骤p。
一部寄存器机能做的所有工作,简单表示就是:结束、增量、减量(或者跳转)。
乍一看,你会觉得这台机器的工作实在有些无聊,只做着一些把一粒豆子放进盒子里或者从盒子里拿出一粒豆子的工作(只要它能找到一粒豆子的话,如果找不到,它会跳转到另一条指令)。但事实上,它可以完成一台计算机能做的所有运算。
让我们从简单的相加开始吧。假设你想让寄存器机将某一个寄存器(比如寄存器1)里的数字加到另一个寄存器(寄存器2)里的数字上。如果寄存器1中的数字是“3”,寄存器2中的数字是“4”,那么,我们要设计出的程序就是:相加结束后,寄存器2中的数字为“7”,因为3+4=7。这个程序用简单的RAP(Register Assembly Programming,寄存器汇编程序)语言编写出来就是:
程序1:相加[1,2]
前两个指令构成了一个循环,寄存器1每减一次,寄存器2就加一次,直至寄存器1中的值变成0,处理单元能“注意”到寄存器中数值为0这一点,继而会将运行步骤跳转到3,即寄存器机停止工作。处理单元并不知道寄存器中的数字,它只能识别出寄存器中数字为0的情况。用盒子和豆子来打个比方:把处理单元想象成是一个盲人,它看不见盒子(寄存器)里面有多少豆子,但是通过探测,它可以识别出盒子空了。所以,尽管事实上处理单元不知道寄存器里确切的内容,但只要步骤1开始运行,寄存器1的内容(不管寄存器1中的数字是几)就会累加到寄存器2里(不管寄存器2中的数字是几),然后停止。(你能知道它为什么会这样运作吗?多试几个例子看看。)我们甚至可以这样说:即使不知道哪两个数相加,不知道数字是什么,甚至也不知道加法是什么,寄存器机也能完美地完成累加计算!
练习1
a.运用程序1,想一下寄存器机需要几步可以计算出2+5=7?(“结束”也算是一步。)
b.要计算出5+2=7需要几步完成?(你从中可以得出什么结论?)(38)
有一种图解可以很好地说明这一运算过程,我们称之为流程图。圆圈中的数字代表将要接受运算的寄存器的地址(它不是寄存器中的内容),“+”代表增量,“-”代表减量。程序从α(alpha)开始,至Ω(omega)处停止。箭头表示进入下一指令。请注意,每个减量指令处都会向外引出两个箭头,可以减量时有箭头指向一个步骤,而无法减量时(当寄存器中的内容为0时,遇0跳转)则有另一个箭头指向另外一个步骤。
现在,我们就来编写一个程序,将一个寄存器中的内容移动至另外一个寄存器中。
程序2:移动[4,5]
流程图如下所示:
可以看到,第一个循环是清空寄存器5。这样一来,不管一开始寄存器5中的数字是多少,它都不会影响到在第二个循环中要移进来的内容——第二个循环正是增量运算的环节,将寄存器4中的内容添加到已清空的寄存器5中去。这种初始化步骤被称作寄存器清零。它是一项非常有用的标准化运算,我们会不断用到它,好让寄存器能重新投入使用。
第三个程序是,简单地将一个寄存器中的内容复制到另一个寄存器中,被复制的那个寄存器中的内容保持不变。仔细观察这个流程图,思考它的程序设计:
程序3:复制[1,3]
显然,这是完成拷贝的一条迂回路线。首先,我们要将寄存器1中的内容移动到寄存器3中,同时比照3中的内容做一个副本,将它复制到寄存器4中。最后,再将寄存器4中的内容重新移动到寄存器1中——也就是将寄存器4中的内容复制回了寄存器1。程序就这样按部就班地运作,不管一开始寄存器1、3、4中的内容是什么,到程序结束时,寄存器1中一定保留着自己原来的内容,复制的内容保存在了寄存器3中。