计算机科学中的抽象 - 符号与值

1 从语言的符号开始

「one」、「一」、「1」、「I」、「いち」是符号,而它们却共同代表的同一种东西,也就是那个数字,那个永远只能是以概念的形式存在,而我们却永远只能用符号来使用的东西。在语言学的理论中,前者被称为能指(signifier),而后者被称为所指(signified)。可以说,符号的抽象是人类最早习得的、也是最基础的抽象。在上面的例子中,符号代指了一个抽象的概念,有时候也会代指某个实在之物,例如我的名字就是我这个人的一个代指的符号。毫无疑问,我们最早学会的抽象就是认识到某个实在之物的名字与这个实在之物本身是不同的。随后我们学会了概念,这又在抽象上向前走了一步。

2 符号与它指的东西从来没有什么必然性

我们会发现,某些符号在一定的语境(context)下是不容改变的,例如,当我们使用10这个符号时,它指的的东西不会是2。但是,如果语境变了,在二进制下,这个符号指的东西却是2了。但无论如何,它们的意义不常变化。而另外一些符号,例如名字“张三”“李四”,或是某些间接的能指“第三个人”等等,决定他们意义的语境,或者说环境(environment)却轻易的会变化。有些符号的意义常常变化,有些符号的意义不那么经常变化,但是总的来讲无论是那种符号,它指的东西都和它没有任何必然的绑定,正如汉语中的「yi」是数字,而日语中的「いい」是一个表示「好」的形容词一样。

3 回到编程语言

上面所说的知识,在我们任何心智健全的成年人来看,似乎是天经地义的、平凡的知识,但事实却似乎出现了偏差。我们会发现几乎完全一致的问题出现在计算机程序设计语言中时,令很多人陷入了困扰,这在初学者身上,特别是尚不习惯计算机中抽象的初学者身上尤为严重。实际上,本文正是为了解决有太多的人曾经在这上面犯迷糊,又来找我求得答案的情况而作的。

4 作为符号的变量

我们在编程语言的入门课的开课不久都会学习到变量的概念,许多教师会将变量类比为某种贴着标签的、具有某种形状的容器,这种容器可以让我们因着它标签上的名字从中取出东西,或者说放进去形状合适的容器。这种类比,总的来讲算不上错误,但是却过于具体又令人容易产生误解了。实际上,这种类比有些多余了。最简单的审视变量的角度即是,按照它本应的样子,将它看作是一种符号,一种指向值的符号。

5 变量是抽象的符号

5.1 计算机不需要变量名

在自然语言中,符号虽然是对它所指的东西的抽象,但是符号本身几乎又是具体的。但是对于变量而言,它并不需要是具体的。这也就是说,在人和人的交流中,虽然正如前面所分析的,符号是自由的,但是这种自由又总是在一定语境下得到了限制。而对于变量来讲,它的具体形式,即我们所写下的变量名,对计算机而言毫无意义。初学者常常会在这里犯了迷糊。 即使是我所在的浙江大学这样一所学生水平国内较高的学校,也有许多人在这里遭遇困难。他们常犯的一个错误是,他们为一个变量起了一个名字,便混淆了名字与事实。例如,他们先是定义了一个布尔型的变量 bool isNight; ,随后在一个条件语句中使用它 if(isNight) {…} else {…} ,在这里他们就开始混淆起来,他们开始困惑 「如果 是晚上 ,那else又有什么用的?难道还能不是晚上吗?可是已经是晚上了呀……」。别笑,这里的问题看似愚蠢,但是不下有十个人问过我,虽然最近没什么人这样问了,但是不知道他们是习惯了还是真的分清楚了。 所以我们发现,对于计算机而言,一个变量实际上只有两样东西,它的类型,以及它的 Identity ,对于某些动态类型的语言,类型也免了。Identity 是什么意思呢?这个词是我随手拿来用的,我不清楚别的地方是否也这样用,但是我感觉它概括的很好。它是说只要我们能够区分两个变量而不引起混淆,变量就可以完成代指值的符号的功能了。而这种区分方式,抽象的来讲,我们也不需要知道,实际上它就是能够区分了,对于编程语言而言。

5.2 变量名是指变量的符号

变量是指值的符号,而变量名则是指变量的符号。我们作为人类,实际无法直接操作变量,我们只能借助它的符号——变量名去操作变量,并借助变量这一符号去操作背后的值。 所以对于计算机而言,变量仍有意义,而变量名则无意义。只不过许多人分不清变量名与变量,于是在学习了汇编语言等之后误以为编程语言经过编译以后就不存在了变量。真实的情况是没有了变量名,但是变量仍然保持了其本身的结构。

6 继续符号与值的讨论

当我们使用赋值语句时发生了什么?这里我们考察的是赋值本身的语义,而不考察奇奇怪怪的操作符重载带来的混乱。我们说,变量的值改变了,或者说变量的内容变了。后者比前者更不容易引起人们的误解,因为前者总会有人有一种 值 变了的错觉,实际上不是变量的值变了,而是变量这个符号所指的东西不同了。这里的内容虽然平凡,但是我们也必须小心的分清二者的区别。因为,赋值不改变值,它改变的是变量,是符号;而另外一些操作却实实在在地改变的是值。

在初学者那里, list.append(3) 与 a=3 这两种截然不同的东西经常被混淆为同一种东西,这就是因为他们没能分清符号和值。他们说,「没错啊,这两个都改变值了啊?第一个让列表多了一个元素,第二个让a的值变成了3。」但是实际上来讲,第一个改变的是值,后者改变的是符号。要说清楚这一点,我们只需要举一个最简单的例子,如果在 a=3 之前a的值是5。那么如果说 a=3 改变了值,难道他是把5变成3了吗?初学者会说「没错啊,确实是从5变成3了」,不错,但是那是 (a)从 5变成了3 ,不是 5变成了3 ,前者变得是符号,后者是值。如果真的是值变了,那么无疑是在说5变了,5变成3了,多么荒谬的结果!

关于最基础的问题的讨论,就到这里为止了,毕竟这个问题本质上还是一个简单的问题,再啰嗦下去有凑字数的嫌疑。我相信上面的这些(已经过于啰嗦的)文字,已经足以了。在这里,重要的是,当你思考问题的时候,要把话说清楚,给自己说清楚,把每个晦暗不清的地方讲通了,就不至于混淆了。

7 额外的赠品,关于指针

在大多数情况下,指针都是作为一个底层概念来使用的,与地址绑定在一起。但是如果我们到抽象一点的层次来看呢?我们知道,在传统的编程语言中有函数,而在函数式编程语言中,有作为第一公民(first class)的函数,也就是说函数可以作为值存储,传递;也有高阶(Higher Order)函数,也就是说,以函数为参数或是以函数为返回值的函数。那么,基于同样的视角我们会发现,指针其实就是一种作为第一公民的符号,我们可以将这种指针作为参数传递,使用;以及高阶符号,指向符号的符号。

当然,这里的说法只是一种抽象视角的尝试,就C语言本身的指针来讲倒是十分单纯,就是纯粹的指向内存地址的东西而已。实际上一旦你去接近底层,再来做这样的抽象就有些困难了。这里也无意过多讨论。

这篇文章是给编程语言的初学者而写的,原因自然是因为常常有人问我各种各样的问题,我在回答过程中发现他们的错误很多时候都归结在了本文所讨论的一些地方。对于编程熟练的人来说,这篇文章的内容自然是无需多看了。