本文作者:Ruby之父松本行弘。
大家有过创造编程语言的经历吗?我想大多数人会回答没有。
对于有编程经历的人来说,编程语言是非常亲切的存在,我们习惯了接受现成的编程语言,可能没有想过要自己去创造一门新的编程语言。这也是情理之中的事情。
但其实我们大家都知道,世界上所有的编程语言都是由某个地方的某个人创造的。它们不是自然产生的,而是根据明确的意图和目的被设计并实现的。如果过去没有这些创造编程语言的人(编程语言的作者),可能我们今天还在用汇编语言编程吧。
其实,创造编程语言不仅可以提升我们作为技术者的价值,而且还可以使我们从中获得很大的乐趣。而且随着开源的普及,创造新编程语言的门槛一下子降低了许多。通过实际创造一门新的编程语言,我们可以学到编程语言的设计思路和实现方法。
可能有的读者会想:“现在再创造编程语言还有什么意义呢 ?”我们稍后回答这个问题,现在我们先来看一下编程语言的历史。
在人们刚开始编程时,编程语言就随之出现了,可以说编程的历史就是编程语言的历史。
早期的编程语言是由在工作中切切实实与编程语言打交道的人创造的,这些人大多就职于企业的研究所(比如 FORTRAN、PL/1 的发明)、大学(比如 LISP)以及标准委员会(比如 ALGOL、 COBOL)等。也就是说,设计开发编程语言是专业人士的工作,但是这个传统随着 20 世纪 70 年代计算机的普及开始发生了变化。一些计算机爱好者在拥有了自己的计算机后,出于兴趣开始编程,甚至开始开发新的编程语言。
其中最具有代表性的就是 BASIC 语言。BASIC 语言原本是美国达特茅斯学院用于教学的编程语言,它的语法非常简单,用极少的代码实现了最基本的功能,所以深受 20 世纪 70 年代编程爱好者的喜爱,并被他们广泛使用。
这些编程爱好者也开始开发自己版本的 BASIC 语言。当时,个人计算机① 的内存顶多几千兆,他们开发的 BASIC 语言就是可以在内存如此之小的机器上工作的小规模版本。这些小规模的 BASIC 程序大小不到 1 KB,它们在 4 KB 左右的内存上也能工作,跟现在需要大内存的语言处理器比起来真是令人惊讶。
① 通常称为微机。微机是微型计算机、微型机的简称。
微机杂志的时代
以个人开发的 BASIC 为代表的小规模语言(Tiny 语言)处理器不久便以各种各样的形式进行了发布。当时的软件有的以 Dump list 的形式刊登在计算机杂志上,有的将程序数据进行音频转换后收录在杂志附带的薄膜唱片(sonosheet)中发布。现在的人恐怕已经不知道薄膜唱片了吧。薄膜唱片是指塑料做的薄薄的唱片,不过唱片这个词几乎没有人用了。据说当时的计算机爱好者都用唱片播放器连接计算机来读取数据,而不使用磁带录音机这个最普遍的外部存储设备。
20 世纪七八十年代是计算机杂志(当时称为微机杂志)的全盛时期,在日本以下 4 种杂志竞争激烈。
这 4 种杂志中现在只有 I/O 仍在发行,不过也大不如前了。作为一个了解当时情况的人,我的内心充满了无限感慨。
这之后,My Computer 杂志派生出了 My Computer BASIC Magazine,又发生了很多事情,继续讲下去恐怕就会变成上岁数人的叙旧了,所以点到为止吧。如果去问问现在三四十岁的程序员,相信他们中间很多人都会眉飞色舞地讲起那个年代的事情。
当时的微机杂志附带了收录 BASIC 的薄膜唱片,除此之外还介绍了其他几个小规模语言,如 GAME、TL/1 等。这些语言都反映了当时那个时代的特色,非常有趣,我们在本文的最后对其进行了介绍,请大家务必读一读。
个人创造编程语言的现状
为什么从20 世纪70 年代后期到80 年代前期开始兴起个人创造编程语言了呢?我认为最大的原因是当时难以获取开发环境。
20 世纪70 年代后期广泛使用的微机是TK-80(图1)那样的主板裸露在外的单板机,很多都是半成品,需要自己去钎焊。这样的机器不可能自带开发环境之类的东西,软件都要自己输入机器语言之后才会工作。
20 世纪70 年代末期才出现PC-8001 和MZ-80 那样的“成品计算机”。然而,这种计算机顶多带一个BASIC 开发环境,因此人们很难自由地选择开发语言。虽说市面上也有商用的语言处理器,但C 编译器的定价就要19.8万日元(约等于13.1万人民币),这不是普通人可以轻易买得起的。于是,人们便有了热情去创造一门自己的编程语言。
可现在获取语言的开发环境已经不再是麻烦事了。各种编程语言和开发环境作为开源软件被公开,即使是非开源的,也可以轻松地通过网络得到免费版本。
这样一来,现在自己创造编程语言岂不是没有任何意义吗?我认为,这个问题的答案为“否”。即使是现在,自己创造一门新的编程语言也是有意义的,而且有很重要的意义。
而且现在很多广泛使用的编程语言也都是在开发环境容易获取的情况下,由个人设计和开发出来的。如果个人开发编程语言真的没有意义,那么Ruby、Perl、Python 和Clojure 这些语言也就不会诞生了。
不过即便如此,我认为Java、Javascript、Erlang 和Haskell 这些语言也可能会以其他形式出现,因为它们会作为业务和研究的一环被开发出来。
那么如今个人设计开发编程语言的动力究竟是什么呢?
回顾我自身的经历以及参考其他语言作者的意见,我认为有以下几点理由。
首先,编程语言的实现可以说是计算机科学的综合艺术。作为语言处理器的基础,词法分析和语法分析也可以应用在网络通信的数据协议的实现等方面。
实现语言功能的库和实现其中的数据结构,这正是计算机科学要做的事情。尤其是编程语言的应用范围广泛,很难事先预测会被用于什么方面,因此库和数据结构的实现难度也就更大,但也变得更加有意思了。
另外,编程语言还是人与计算机间的接口。设计这样的接口,就需要深入考察人是如何思考问题的、下意识中有什么样的期待。反复进行这样的考察,对编程语言之外的应用程序接口(API)设计、用户界面(UI)设计,甚至用户体验(UX)设计都是有益的。
提升个人品牌
也许有人会感到意外,实际上在IT 行业,对编程语言感兴趣的人不在少数。这是毋庸置疑的,因为编程与编程语言有着切不断的关系。以编程语言为主题的活动和会议等往往都会吸引很多人参加,由此我们也能感受到编程语言的魅力。正因如此,很多人在网上发现新的语言后就会开始尝试。就拿Ruby 来说,它在1995 年被发布到网上之后,仅仅2 周左右就吸引了200 多人加入邮件列表,着实令人惊讶。
可是,虽然有很多人愿意尝试使用新的编程语言,却几乎没有人会去设计并实现一门编程语言,而且是超越杂志提及的“小儿科语言”那种程度的能够实用化的编程语言。但我保证,仅凭设计出一个实用的编程语言这一点,你就会得到人们的尊敬。
在这个开源的时代,技术人要想生存下去,在技术社区的存在感是非常重要的。虽然技术人只要开源其软件就能达到站稳脚跟的效果,但编程语言的“特殊感”会进一步提升其品牌效应。
乐趣第一
另外,编程语言的设计与实现比任何事情都更有趣。的确如此。与计算机科学相关的具有挑战性的工程也是这样。设计编程语言还可以帮助使用这门语言的程序员思考,甚至左右他们的想法,这一点也非常有意思。
通常来说,编程语言有一种从别处获取的、不容侵犯的感觉。如果是自己创造编程语言,就完全没有这个问题。你可以按照自己的喜好进行设计,如果不满意或者有更好的想法,也可以自由地修改。从某种意义上来说,这是终极的自由。
编程在某种意义上是对自由的追求。通过亲自编程,我们可以获得单纯使用他人的软件时享受不到的自由。至少对我来说,这是编程的一个重要动机。于我而言,创造编程语言是获取更高程度自由的手段,也是我的乐趣与快乐的源泉。
为什么创造新编程语言的人不多
虽说自己创造一门编程语言有这么多好处,但并不是每个人都会去做。正如上文所说的那样,对编程语言感兴趣的人虽然有一些,但着手去创造编程语言的人几乎没有。说是“感兴趣的人有一些”,但从占总人口的比例来看,其实少到可以算作误差范围的程度,更不用说有动力去创造新编程语言的人了,就算没有也不足为奇。
我自己在关注编程语言几年后就着了迷,但是在进入大学主修计算机科学之后,才注意到并不是所有人都对编程语言感兴趣。这是因为我在偏僻的乡下长大,周围没有喜欢编程的人可供比较。
这一点对我来说也不知道是幸还是不幸。
“难道我跟别人不一样?”意识到这一点的时候,我很震惊。因为当时的微机杂志上刊登了很多关于TL/1 等编程语言的文章。我本以为对编程感兴趣的人(和我一样)很可能也会对编程语言着迷,但实际上并非如此。
本来就对编程语言不感兴趣的人自不用说,即使是感兴趣的人,也很难走到自己设计并实现编程语言这一步。
关于这个问题的原因,我思考过很长时间。作为编程语言设计者,在参加编程语言相关的活动时,我也曾以过来人的身份鼓励别人尝试一下,但结果总是不尽如人意。当然,万事开头难,开始一件新的事情是需要很大勇气的。但即使是这样,反响也太差了。
没必要想得很难
问了很多人之后,我才知道大家为什么不去着手尝试了。那是因为就算有兴趣创造一门新的编程语言,在开始之前多半也会有某种心理障碍,也就是觉得“编程语言有现成的,本来就不需要自己去设计和开发”。难得有那么几个人不会产生这种心理障碍,却又觉得语言的实现似乎很难。也就是说,他们觉得编程语言很有趣,自己也想做做看,却不知道如何去实现。
仔细想来,关于编程语言的实现的书虽然出乎意料地出版了很多,但大部分都是大学教材的难度,非常不容易理解。另外,与编译原理有关的“文法类型”和“Follow 集合”等晦涩的术语也频繁出现。
但是认真想一想,我们的目的是出于兴趣创造自己的编程语言,而不是去掌握编程语言的实现所需的所有知识。如果你认为在没有完全掌握正确的知识之前就无法着手创造编程语言,那就大错特错了,你的热情会被逐渐消磨殆尽。
成就一番伟大的事业首先需要的就是热情,不能保持热情是不行的。一旦有了创造编程语言的热情,就应尽快开始,以后再根据需要慢慢地掌握所需的知识即可。
这推荐松本行弘新书《松本行弘:编程语言的设计与实现》。
本书主要介绍创造简单的语言处理器所需要的基本知识以及工具的使用方法,并不涉及编程语言实现的较难部分。相较于理论背景,作者把重点放在了如何设计编程语言上。
GAME
GAME(General Algorithmic Micro Expressions)是由BASIC 派生的Tiny语言。它最大的特征是关键字全部是符号,以及所有的语句都是赋值语句。
例如赋值给“?”时会输出数值,反过来将“?”赋值给变量时会要求输入数值。字符串的输入输出使用“$”。另外,将行号赋给“#”时为goto语句,将行号赋给“!”时为gosub语句(调用子程序)。
另外,像"ABC"这样的一个字符串语句会打印出字符串,后面有“/”的话会换行。
这是一门非常有意思的编程语言,示例代码如图1-A所示。它既像BASIC,又不像BASIC,请大家好好感受一下。
100---------------- Comment -------------------
110| 如果紧接在行号之后的不是空白,则将该行作为注释
120--------------------------------------------
130
200 / " FOR循环语句 是 变量名=初始值,最终值 ... @=(变量名 + 步长) " /
210 A=1,10
220 ?(6)=A
230 @=(A+1)
240
300 / " IF语句的例子 " /
310 B=1,2
320 ;=B=1 " B=1 " /
330 ;=B=2 " B=2 " /
340 @=(B+1)
350
400 / " 数值输入与计算 " /
410 "A = ?" A=?
410 "B = ?" B=?
420 "A + B = " ?=A " + " ?=B " = " ?=A+B /
430 "A * B = " ?=A " * " ?=B " = " ?=A*B /
440
500 / " 数组与字符输出 " /
505--------令数组的地址为$1000
510 D=$1000
520 C=0,69
525--------作为2字节数组写入
530 D(C)=(C+$20)*256+C+$20
540 @=(C+1)
560 C=0,139
570--------作为1字节数组读取,并输出为字符
580 $=D:C)
590 @=(C+1)
600
700 / " GOTO 与 GOSUB " /
710 I=1
720 I=I+1
730 !=1000
731* ?(8)=I*I
740 ;=I=10 #=760
750 #=720
760
900 / "程序结束 " /
910 #=-1
920
1000 / " 子程序 " /
1010 ?(8)=I*I
1020 ]
图1-A GAME 语言的示例代码
GAME是一门非常简洁的语言,用8080汇编语言编写的解释器的大小还不到1 KB。另外,由中岛聪(当时居然还是高中生)开发的使用GAME编写的GAME编译器,代码仅有200行左右。真不知道我们应该惊叹GAME 的语言表现能力,还是中岛聪的技术能力。
TL/1
同一时期在ASCII 杂志上发表的Tiny语言中还有TL/1(Tiny Language/1),它的名字应该是模仿了美国IBM公司开发的编程语言PL/1。与受BASIC影响使用符号的GAME语言不同,TL/1拥有类似于Pascal的语法,让人觉得更加“正常”。另外,TL/1的语言处理器是编译型的,与主体为解释型的GAME比起来速度更快。但实际上GAME也有编译器,这一点我们在文中介绍过。
TL/1的特征是语法类似于Pascal,以及变量类型只有1字节的整数。各位读者也许会想这样怎么编写代码,不过当时的主流CPU是8位的,所以TL/1设计成这样也不是很怪异。虽说是运行在8位CPU 上,但包括GAME 在内的其他语言都提供了16位的整数类型。
那么1 字节无法表示的超过255 的数值该如何编写呢?答案是按字节进行分割,用多个变量组合表示。比如用2 个变量保存16 位整数,边看计算溢出时的进位标志边计算(图1-Ba)。在当时的8位CPU上,大部分处理用16位整数就已经足够了(地址用16位的话就可以访问所有地址空间)。作为Tiny语言,这样的功能已经足够了。各位读者如果有兴趣,可以显式地查看进位标志,使用多个变量进行24位计算或32位计算。
% (a)
% 以"%"开始的行是注释,当时不能使用日语
BEGIN
A := 255
B := A + 2 % overflow
C := 0 ADC 0 % add with carry
END
% (b)
BEGIN
WRITE(0: "hello, world", CRLF)
END
图1-B TL/1 语言的示例代码
可以处理指针和字符串
另外,指针也无法仅用1字节来表示。这里用mem数组进行访问,也就是说,用下面表达式中hi,lo表示的16位地址来访问指定地址的内容。
mem(hi, lo)
下面是将地址的值替换为v。
mem(hi, lo) = v
当时的个人计算机(微机)最多只有32 KB 的内存,所以能用16 位地址访问就已经足够了。还有就是字符串。当然,我们也可以将字符串当作字节数组,对每个字节依次进行操作,但是这样处理太麻烦,因此TL/1设计了用于数据输出的WRITE语句。
例如,用TL/1开发的Hello World程序如图1-Bb所示。TL/1中变量本应只有1字节的整数,却出现了字符串。实际上,WRITE是为了能够处理字符串而单独增加的语法。
WRITE之外的语句是无法处理字符串的,所以不能进行普通的字符串处理,只能够操作1字节的整数。现在看来可能会觉得很不可思议,但是在不属于Tiny语言的Pascal和FORTRAN中,输入输出也是被特殊处理的,这在当时也许是一种比较普遍的做法。
——本文内容来源《松本行弘:编程语言的设计与实现》。
为什么要创建一门新语言?新语言开发中会遇到什么困难?
C、Java、Python等语言的设计为什么是现在这样?
什么样的语法不会给使用者造成负担?
Ruby 之父松本行弘全面披露新语言开发的整个过程,以新语言Streem 的设计与实现过程为例,作者从设计新语言的动机开始讲起,由浅入深,详细介绍了新语言开发中的各个环节,以及语言设计上的纠结与取舍,其中也不乏对其他编程语言的调查与思考,向我们展示了创建编程语言的乐趣。