发号施令的等号:x = x + 1 与编程语言
对数学家来说,
$$x = x + 1$$这一行是一个无解的方程:两边同减 $x$,剩下 $0 = 1$。对程序员来说,这是最常见的一行之一,意思是“取出 $x$ 的值,加一,把结果存回 $x$”。同一个符号,在两个群体里做着相反的工作:在那边它断言一个等式;在这边它命令一次赋值。
Fortran 选择了等号
始作俑者是 Fortran,第一种被广泛使用的高级编程语言,由约翰·巴克斯领导的团队在 IBM 创造,1957 年发布。程序打在穿孔卡片上,一张卡片一条语句,而穿孔机的键盘上符号很少。为了说“把值 $27$ 存进 $N$”,Fortran 写作 N=27,就像工程师的公式一样。
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$”,因为在数学书写中,条件里的等号只能是比较。