周而复始

按语:这是我在爱丽丝掉进去的兔子洞里为「不懂编程的人」写的系列文章的第五篇,整理于此。它的前一篇是《原子与虚空》,笼统地讲述了 Emacs Lisp 程序或任意一个计算机程序的基本成分。

无论是汽车、轮船还是火箭,所有的交通工具里面,都有一个被称为发动机的东西。即使我们徒步或骑着单车,这里面也有一个发动机——我们的心脏。计算机里面也有个发动机,就在 CPU 里。

所有的发动机,它们的本质在于周而复始。

在发动机来看,自己就像是一头蒙着双眼的在拉磨的驴子,不知道自己在做什么,但是依然在磨道上一圈又一圈不知疲倦地行进。

完全可以说,有了发动机,就有了一切。因为发动机不仅能够输出动力,也能够创造时间——发动机循环运转次数。有了动力,又有了时间,还有什么运动形式是无法创造出来的呢?

让一辆汽车,前行 100 公里,本质上是汽车发动机运转的轨迹在地球的表面的展开结果。让火箭飞出天外,本质上是发动机运转的轨迹在三维空间中展开的结果。我们徒步走 1 公里,是我们心脏搏动的轨迹在地球表面的展开结果。

倘若世界的运转真的是受了一部发动机的驱动,那么这个发动机的存在,我们无法直接感受到,我们只能感受到它的周而复始的运动。这给哲学家们制造了一个巨大的想象空间。既然我们能够感受到它的运动,是出于我们对自身以及万物的观察。我们发现,这种运动可以在不同的物质之间传递,从而衍生出在我们看来气象万千的运动形式。

图片来自网络,侵删。

或许,正是因为发动机的周而复始的运动,才导致了万物之间总是能够精确传递这种运动。当牛顿发现,他作为一个凡人,经过很简单的计算,就能够精确算出天体的运动,而且整个宇宙就像一部复杂的钟表那样精确运行。这种事,似乎也只有上帝能够做到。所以,牛顿的科学研究活动以他在哲学上引入了上帝作为宇宙第一推动力而终止。牛顿回归上帝的怀抱,这其实完全可以视为是那个时代一个充满理性的科学家在认真思考之后的值得尊敬的决定,不过这事却被后人视为变了味,认为牛逼如牛顿,也难免晚节不保。

我们在计算机里用着各种复杂的软件,Alpha Go 在围棋领域天下无敌,自动驾驶呼之欲出,这一切在不会编程的人看来,是复杂的,是深奥的,是精确的……然而在懂编程的人看来,这一切都是建立在 CPU 周而复始之上。

我们可以将计算机里的每个程序视为由 CPU 驱动的一部机器,是 CPU 的周而复始的运动驱动着这部机器里的齿轮、连杆、凸轮、涡轮、链条等传动部件,从而决定了这部机器的精确性。

计算机里没有上帝。CPU, 也如上文所言,不过是一只蒙着眼睛在磨道上周行的驴子。如果我们愿意,完全可以通过程序模拟一个 CPU。例如 Emacs Lisp 解释器,它的功能就相当于 Emacs 计算机的 CPU。

很容易制造一个可由 Emacs Lisp 解释器驱动的空转着的齿轮:

(defun 齿轮 ()
  (齿轮))

(defun 驱动齿轮 ()
  (interactive)
  (齿轮))

向 Emacs 发送 M-x 驱动齿轮 <RET>,Emacs Lisp 解释器便会对 齿轮 函数进行求值,结果这个函数又对自身进行求值,因此便形成了周而复始的「转动」。通常将这种形式的函数求值称为递归求值。

一个函数,在其内部对自身进行求值,这就是递归。

齿轮 函数除了对自身进行求值之外,没有做任何事,因此虽然能够让 Emacs Lisp 解释器完成求值,但理论上上不会得到任何输出结果,这个函数会沦陷在对自身进行求值的过程中而不能自拔。不过,由于 Emacs Lisp 解释器为函数对自身的求值的重复次数设置了上限(默认是 1000 次,允许程序员修改),所以 Emacs Lisp 解释器对 齿轮 函数的求值结果是程序运行出错。

Emacs 设置函数对自身求值次数的上限是有道理的。倘若某个函数无限次的陷入对自身求值的状态,那么 Emacs 就死机了。因为 Emacs 每个时刻只能允许一个程序在运行。一个陷入无限次对自身求值的程序,会导致其他程序再无机会运行。

总体上来看,我们的世界不像 Emacs 这样。不过时不时也会出现「既生瑜、何生亮」的故事。

递归是周而复始的一种运动。当我们在一个递归函数中执行一些对我们有意义的表达式求值时,这就类似于将一个齿轮的运动传递给了其他部件。下面,我们可以对齿轮程序进行一些修改,限定它的运转次数,并且让这个齿轮不停地驱动 insert 函数:

(defun 齿轮 (m n)
  (if (< m n)
      (progn
        (insert (format "%d-" m))
        (齿轮 (+ m 1) n))
    (insert (format "%d\n" m))))

(defun 驱动齿轮 (n)
  (interactive
   (list (read-number "运转次数:")))
  (齿轮 1 n))

当再次向 Emacs 发送 M-x 驱动齿轮 <RET>,并向 驱动齿轮 函数传递的参数值为 10 时,Emacs 的当前缓冲区会显现:

1-2-3-4-5-6-7-8-9-10

这就是我们制造的齿轮在 Emacs Lisp 解释器的驱动下,运转 10 次之后的结果。

上述代码出现了 prognif 等表达式。它们的含义,在下一篇阐述。现在只需要专注于函数的递归求值能够产生一种类似于发动机的效果。对于受发动机驱动的任何一个传动部件而言,它是下级部件的发动机。由于 齿轮 函数是一个递归函数,在其内部对 insert 函数进行了求值,那么 齿轮 函数就是 insert 函数的发动机。此外,齿轮 函数的递归求值过程也对它的第一个参数进行了修改,因此 齿轮 函数也是向这个参数输出动力的发动机。

无论多么复杂的计算机程序,它们都是受 CPU 驱动的具有层次传动机制的机械系统。我这么说,可能会让他人认为我是个决定论或宿命论患者。不过,我却为自己与自然万物共享一个 CPU 而觉得三生有幸。还有,这个 CPU 似乎也没有替我决定什么,它只不过向我提供了动力。

下一篇红药丸,还是蓝药丸


如果觉得我的文章对你有用,请随意赞赏
载入中...