对数学家来说,

$$x = x + 1$$

这一行是一个无解的方程:两边同减 $x$,剩下 $0 = 1$。对程序员来说,这是最常见的一行之一,意思是“取出 $x$ 的值,加一,把结果存回 $x$”。同一个符号,在两个群体里做着相反的工作:在那边它断言一个等式;在这边它命令一次赋值。

Fortran 选择了等号

始作俑者是 Fortran,第一种被广泛使用的高级编程语言,由约翰·巴克斯领导的团队在 IBM 创造,1957 年发布。程序打在穿孔卡片上,一张卡片一条语句,而穿孔机的键盘上符号很少。为了说“把值 $27$ 存进 $N$”,Fortran 写作 N=27,就像工程师的公式一样。

打着 Fortran 语句的穿孔卡片。第二张卡片上的 `N=27` 并不是断言 N 等于 27,而是命令把 27 存进 N。
打着 Fortran 语句的穿孔卡片。第二张卡片上的 N=27 并不是断言 N 等于 27,而是命令把 27 存进 N。Tangopaso · 维基共享资源 · 公共领域

这个选择出于实际考虑,并且延续了下来。但它让那些把编程看作数学的人感到不安。由一个国际委员会在 1958 至 1960 年间创造的 Algol 语言,用另一个符号 := 表示赋值,而把 = 留给比较。瑞士人尼克劳斯·维尔特在 Pascal(1970)中保留了 :=,高德纳在《计算机程序设计艺术》中用箭头写赋值:$x \leftarrow x + 1$。艾弗森的 APL 也用箭头,统计语言 R 则把它继承为 <-。

C 语言的 ==

1972 年创造的 C 语言沿用了 Fortran:= 表示赋值。为了比较,它需要另一个符号,于是把等号写两遍:==。其后果是编程史上最著名的错误之一:

if (x = 0) { ... }

程序员本想询问 $x$ 是否为零。他只写了一个等号,结果命令把零放进 $x$。由于赋值的值是 $0$,而 C 把它读作“假”,这段代码永远不会执行,$x$ 原来的值也悄无声息地丢失了。许多现代编译器遇到这一行会发出警告,正是因为对来自数学的人来说,这个符号有两种意思。

数学得到了什么

赋值不是等式,但它把传统数学几乎从不写出的一个想法带进了书写:时间。在 x = x + 1 中,左边的 $x$ 和右边的 $x$ 是同一个名字在两个时刻。数学用下标来表达这一点,$x_{n+1} = x_n + 1$,就像在数列和牛顿迭代法中那样。编程表明,可以把名字想成一个内容会变化的盒子来写出整个算法,而这种思维方式又通过算法书回到了数学中。

对学习者来说,两种读法难以和平共处。第4章表明,学校里的等号本来就身兼多职;计算机的等号又加了一种,即命令。分辨“我断言”和“使之成为”,是从一个群体进入另一个群体时要学会的事情之一。

练习:跟着程序走

一个程序依次执行:x = 3、x = x + 1、x = 2 * x。$x$ 的最终值是多少?如果这三行是方程呢?

作为程序:$x$ 先是 $3$,然后是 $4$,最后是 $8$。作为方程,方程组 $x = 3$、$x = x + 1$、$x = 2x$ 无解(第二个不可能成立,第三个只在 $x = 0$ 时成立)。用下标改写为 $x_0 = 3$、$x_1 = x_0 + 1 = 4$、$x_2 = 2x_1 = 8$,三行又都成了正确的等式。

练习:if 的错误

在 C 中,为什么 if (x == 0) 可行而 if (x = 0) 不行?数学家会怎么写?

x == 0 比较,根据 $x$ 的值为真或为假。x = 0 把零赋给 $x$,其值为 $0$,被当作假:条件永远不满足,而 $x$ 被清除了。数学家会简单地写“若 $x = 0$”,因为在数学书写中,条件里的等号只能是比较。


本章目录 · ← 作为思维工具的记号:艾弗森 · 定理上的名字:斯蒂格勒定律 →