Eric Way's Personal Site

I Write $\sin(x)$ Not Tragedies

编程语言与自然语言之比较

2020-04-21 Coding

  1. 1. 引言
  2. 2. 形式
  3. 3. 关键字
  4. 4. 规则的多样性
  5. 5. 编程范式
    1. 5.1. 过程式编程
    2. 5.2. 函数式编程
    3. 5.3. 面向对象编程
  6. 6. 不同词性的词
    1. 6.1. 名词和代词:值和变量
    2. 6.2. 形容词:名词化
    3. 6.3. 动词:函数
  7. 7. 词典或文档
  8. 8. 结语

浅谈编程语言和自然语言的可比性。

引言

语言是沟通的工具。沟通意味着两个过程:输入和输出。自然语言的听说读写,听和读是输入,读和写是输出。编程语言也有输入和输出的两个过程,但其特殊性在于,它的输入者(读代码的一方)需要是计算机和(很多时候,比如修改代码时,也需要是)人类,输出者(写代码的一方)往往是人类。计算机和人类各有其所擅长的领域、各有一套迥异的行事模式,这决定了编程语言需要达到人机之间良好的平衡。在迎合人类思维需求的这一方面,自然语言的影子就往往会渗透进编程语言的设计之中。从另一方面来说,编程语言和自然语言的这种可比性,从根本上来说,源自二者皆是对事物发展过程的一种描述。如何对两者进行有意义的比较是一个有趣的话题,同时也是有价值的,因为一方面可以更好理解编程语言中的各种概念,另一方面在某种意义上可以提供对“双语”学习宏观视角上的指导。王垠(2018)就类比了英语语法和程序语法之间的关联。本文一记我对这个话题的看法。

形式

书写上,自然语言依赖于各种字母表或者汉字,编程语言往往由ACSII字符组成,主要是英语字母、数字和各种符号,这些构成要件首先在自然语言中被广泛地运用,再出现在计算机键盘上,为编程语言所采用。由此可见,编程语言的书写形式很大程度上依附于自然语言。

自然语言的另一种形式:声音,是编程语言不强调的,因为自其功能而言,计算机运行代码,并不需要朗读代码。当人们需要朗读代码的时候,编程语言的声音系统更是依附于自然语言的。

关键字

编程语言的关键字对应的是自然语言中的词汇。编程语言一直在尽力使用自然语言(常常是英语)的词汇作为其关键字,以增强其可读性。if传达出的条件控制语义,和英语中“如果”的意思是很贴合的。SQL语言中的SELECT 、FROM、BETWEEN … AND …等等,也是“自然语言可读的”。再如ES6引入的let,让声明(可变)变量的语句直接等于相应的英语语句。新近出现的文言lang之所以让人叫绝,主要是因为其编程语句实现了“文言可读”。

为什么学习较为古老的Shell脚本会让人觉得十分困难?因为它过分依赖了各种符号,而非自然语言作为关键字进行表意。对于计算机而言,只要有明确的对应法则,它总是能轻松对应符号与相应的规则,不会进行任何抱怨;但对于人类来说,记住${ #foo}表示取foo变量的长度是比较困难的,尽管关键字#通常表示数字,但这里表示取长度的对应法则并不一目了然。这就是为什么在当下更为流行的语言,如Python中采用len(foo)、JavaScript中采用foo.length,关键字从自然语言的角度是更容易理解的,很容易看出其表达的意思是”the length of foo“。

规则的多样性

编程语言中的表达式对应的是自然语言中的短语或者句子,一种由关键词加上语法结构组成的结构。语法结构是语言规则的直接体现。

编程语言,出于其自身要求的可实现性,对语法的严谨性要求更强;相比之下,经过长期历史演化的自然语言,通常具有更为松散的语法结构,这体现在各种规则的常常难以用逻辑解构的“例外”上。然而,自然语言中的语法结构中多样且不规则的特征,影响了编程语言的设计,并导致了编程语言中往往具有的语法结构规则的多样性。

为了说明自然语言中规则的多样性,我们不妨先看看一个所谓规则非常简单的自然语言——世界语。以下引用自知乎专栏

构词法:比较有趣的一点,世界语两个意思相反的词,通常用mal或非mal来表示。也就是说,以mal为首的词,都要比其反义词多出一个音节。并且世界语里所有意义相反的词都这么处理。例子:mallonga(世界语),short(英语)。

看见带mal的词时,反应速度依然不快。比如malproksima,我首先一看到mal,只会意识到它是一个词的反义词,再看到proksima(近的),哦,那就是它的反义词“远”啰。总是存在一个思考过程。

而且这还会使没有高低优劣之分的一组反义词不对等,如dekstra(右,right)和maldekstra(左,left),世界语就把优先权搬给了dekstra。

除此之外,我还觉得这使世界语表达的丰富性下降了。

这种构词方式已经开始影响到了我对词义的理解。Rapida的意思是“快”(fast,rapid)。要是malrapida出现在我眼前,它给我的第一印象是“不快”,只有在翻译句子时才意识到要翻译成slow(慢),而不是not fast(不快)。

稍微了解了一点世界语的造词方式后,很容易意识到规则的简单并不是自然语言应该追求的,也不是我们惯用的汉语、英语等自然语言的内在构造逻辑。

如何理解编程语言同样蕴含着语法规则的多样性?简单的例子是,我们可以认为函数也是一种特殊的变量,例如

1
const double = x => x * 2;

1
double = lambda x: x * 2

然而声明函数往往采用的是和声明变量不同的语法结构,即往往不用=去赋值函数,而是用

1
2
3
function double(x) {
return x * 2;
}

1
2
def double(x):
return x * 2

虽然有时这种写法的差异并不是syntactic sugar(差异如递归的可用性),但这揭示了一点:编程语言不惜创造新的语法结构去表达一种较为特殊的情形。这种逻辑是蕴含在自然语言的演化之中的,自然语词的创造往往不是从逻辑出发,而是从人们的对事物的较为直观的认识和知觉出发的。惯用自然语言的人们是习惯于这种特殊性或缺乏逻辑的,这显然是被编程语言的创造者考虑的。

另外一个简单的例子就是代数表达式,比如1 + 1,这是编程语言对自然语言最赤裸的迎合。对于惯用自然语言来表达算数的人类来说,“一加一”是非常寻常的。可是,仔细思考一下+对计算机意味着什么:

  • 如果+是一个函数,那为什么我们不写+(1, 1)
  • 如果+是整数类型的一个方法,那么为什么我们不写1.+(1)

当然,你确实可以在Ruby里采取后一种写法,但一般没有人那么做,因为1 + 1是完全可用而且更加可读的。

最后再说一说语法结构非常简单的语言Racket(或者影响它的Scheme),它排斥语法上的“例外”(所有表达式一律是(e, e1, e2, ..., en),其中e表示过程,e1, e2, ...,en表示参数),因此1 + 1要写成(+ 1 1)。这种语法结构对计算机是非常友好的,但并不非常人类友好,因为程序写出来非常反直觉,另外会有多到令人发指的括号。

总而言之,1 + 1这样的中缀表达式对于编程语言来说是不寻常的,这是一种对计算机来说新颖甚至复杂的规则,迎合着人类使用自然语言的习惯。

编程范式

编程范式,对应的是自然语言中的架构段落或文章的方法。

此处略谈一下 imperative programming 和 declarative programming,这两者分别有个子集是 procedural programming (过程式编程)和 functional programming(函数式编程)。它们的差别和对照不是本文的重点,所以此处不严格区分。最后提一笔OOP。

过程式编程

过程式编程,对照自然语言,更像是一句话一句话说下来。比如随手写一个函数:

1
2
3
4
5
6
7
8
9
function f(x) {
let y = x * 10;
if (x > 0) {
y += 1;
} else {
y += 2;
}
return y;
}

这是典型的 procedural programming,也是典型的 imperative programming。用自然语言是容易解读的。

The function f needs an argument x.

Let y equal to x times 10.

If x is greater than 0, then let y increment by 1; else let y increment by 2.

The returning value of this function is y.

这也不难理解为什么 imperative programming 的名字就是从自然语言中祈使语气(imperative mood)来的。procedural programming 其实是目前大多数编程语言采用的范式,或者大多数人学习编程采用的范式,因为其实这非常符合自然语言类似故事情节的叙述。类似“从前有座山,山上有座庙”:先定义一个山,再用下一个句子补充山的特性或者充实对山的描述。从动作或行为的角度考虑,人们也是常常愿意先做一件事、然后再做另外一件。

函数式编程

与之相对的是 functional programming:

1
2
3
4
5
6
fun f x = 
let
val y = x * 10
in
y + (if x > 0 then 1 else 2)
end

这个过程不是用若干个句子进行描述的,不是先做一件事、再做一件事,最后拿出一个返回值给你;而是,整个函数就是在用各种结构去构造一个最终结果。这里没有句子,只有一个用各种语法结构去修饰的短语作为结果。如果非要用自然语言去表达,那就大概是(为增强可读性加了括号):

The function f of x is ((y plus (1 if x is greater than zero) (2 if not)) where y equals to (x times 10)).

这会更像是一句冗长的“从前有座(上面有个庙的)山”。这种和自然语言相悖的思维方式,很有可能是当下函数式编程不太流行的原因。不过,数据库语言如SQL还是按declarative programming这种思维模式去写的:总是类似SELECT [some column] FROM [some table] WHERE [some condition is satisfied]的架构,但是诸如subquery这样的操作可以把填充进去的东西变得非常复杂。

面向对象编程

面向对象编程,在自然语言中,就是集体和个体的区别。当我们说everyone的时候,其实是在说一个集体,但用的谓语是单数的,原因在于落脚点在”one”上了。写class的定义的时候,其实也是在说明一个集体的特征,但写的时候,也只是在描述每个实例所具有的属性或方法。对于每个个体来说,属性是名词性的(描述性的,往往可以说是名词化的形容词),方法是动词性的(这个实例可以干嘛)。

不同词性的词

自然语言中各种词性的词在编程语言中有一定的体现。

名词和代词:值和变量

“名词”在编程语言中相当于“值”,“代词”相当于“变量”,这基本是不言自明的。

形容词:名词化

在程序的世界中,往往是没有形容词的,因为形容词总是模糊的。我说“大”,你问我“多大”,我只能告诉你是值是多少;我说“红”,你问我“多红”,我得给你一个RGB值。这量化的过程,就把形容词名词化了。不过CSS里面调font-size的时候可以直接写个large,写color也可以直接red,当然这只能算是syntactic sugar,其实内部一定是转换成字号或RGB的。

动词:函数

“动词”的对应是“函数”,因为两者都强调一种“行为”。从编程语言的角度看,pure function强调无side effects,更接近数学上的函数,那就是说,这种行为只侧重于把输入值利用某些规则“变成”输出值,而不产生其他影响;procedural programming中side effects往往是必要的,这更像是复杂的自然世界中各种行为产生的后果,例如带来的各种状态的改变。一个笑谈是,如果把人的一生看成一个pure function,那就是大抵就是

1
const life = person => null

“人生是让人变成虚无的过程。”——因为任何人最终都是要化成一缕青烟的,由此可见人生的意义主要在于side effects。这可能是procedural programming流行的另外一个原因?笔者没有考证过,但不妨确信如此。

如果函数是动词,如何看待first class functions,即把函数看成其他变量作为另外函数的参数或返回值?这很容易让人想到英语中将动词转化成名词性的做法,用doing或者to do的结构。这是很有趣的。

1
2
3
const increment = x => x + 1;
const y = [1, 2, 3].map(increment);
// y = [2, 3, 4]

很容易把map里面的increment想成to increment或者incrementing,像”What map does is to increment every element in [1, 2, 3] and produce a new array”。

文首提及的王垠的文章把动词看成句子的核心,的确,因为编程语言中函数的调用往往也是程序的核心。知道一个函数(function)需要什么输入值、会产生什么输出值、会有什么side effects,才能有效地利用它提供的功能(function)。知道一个动词的用法才能知道怎么用它进行造句,从而使用它提供的表意功能。

词典或文档

可以说,说话作文就是在调用语词的API。词典之于自然语言,正如文档之于编程语言:它们都揭示了我们应该如何去使用这门语言,说明了根本的规则,并提供适当的样例帮助理解。学习语言也需要常常翻看词典,学习编程需要常常查阅文档,这道理是一样的。

编程语言由于自身更强的逻辑性,这种规则更容易得到描述;自然语言的规则有时是模糊的,因此需要更多的样例提供对语词用法的直观的感知,这就是词典中例句或者collocation为什么在语言学习中具有很大的重要性,它们帮助语言学习者理解:在什么语境中这个词语应该被使用?这个词语应该进行什么样通常的搭配?这是调用语词,以达到其表达效果必要的知识。

结语

编程语言和自然语言的比较是一个很大的话题,远远不是这个四千字的短文能穷尽的。本文也仅仅是浅尝辄止,略谈一二,以飨读者。

This article was last updated on days ago, and the information described in the article may have changed.