Two-bit History 发布的文章

想象一下,你坐在河边,河岸上如茵绿草,不远处湍急河流;午后的阳光慵懒惬意,使人陷入冥想哲思,不觉开始思考眼前的河流是否真实存在。诚然,几米外确实有河水奔流而下。不过,我们所称为“河流”的存在究竟是什么呢?毕竟,河水奔流不息,一直处于变化之中。似乎,“河流”这个词无法指代任何固定不变的事物。

2009 年,Clojure 的创始人 里奇·希基 Rich Hickey 发表了 一场精彩的演讲,探讨了为什么上文那样的哲学窘境会给面向对象程序的编程范式带来难题。他认为,人们看待计算机程序中的对象与看待河流的逻辑是一样的:我们想象对象是固定不变的,即使对象的许多或者说全部的属性都无时无刻不处于变化之中。所以,这种逻辑并不正确,我们无法区分在不同状态下同一对象实例的不同之处。程序中没有明确的时间的概念。人们只是单纯地用着同一个名字,以期在引用对象时,对象能够处于预期的状态中。这样,我们也就难免会遇到 故障 bug

希基总结道,这一难题的应对办法就是人们应该将世界建模成作用于不可变数据的 进程 process 的集合,而不是可变的对象的集合。换句话说,我们应把每个对象看作一条“河流”,因果相连。总结说来,你应该使用 Clojure 等函数式语言。

作者在远足途中思考面向对象程序设计的本体论问题。

自从希基发表演讲之后,人们对函数式编程语言的兴趣不断提升,主流的面向对象编程语言也大多都采用了函数式编程语言。尽管如此,大多数程序员依旧沿用自己的老一套,继续将对象实例化,不断改变其状态。这些人长此以往,很难做到用不同的视角看待编程。

我曾经想写一篇关于 Simula 的文章,大概会写到我们今天所熟知的面向对象的理念是何时又是如何应用到程序语言之中的。但是,我觉得写当初的 Simula 与如今的面向对象程序设计的 迥然不同之处,会更有趣一些,这我敢打包票。毕竟,我们现在熟知的面向对象程序设计还未完全成型。Simula 有两个主要版本:Simula I 和 Simula 67。Simula 67 为世界带来了 class 类的继承 class hierarchy 以及 虚拟方法 virtual method ;但 Simula I 是一个初稿,它实验了如何能够将数据和进程捆绑起来的其他设想。Simula I 的模型不是希基提出的函数式模型,不过这一模型关注的是随时间展开的 进程,而非有着隐藏状态的对象之间的相互作用。如果 Simula 67 采用了 Simula I 的理念,那么我们如今所知的面向对象程序设计可能会大有不同——这类偶然性启示我们,不要想着现在的程序设计范式会一直占据主导地位。

从 Simula 0 到 Simula 67

Simula 是由两位挪威人 克里斯汀·尼加德 Kristen Nygaard 奥利-约翰·达尔 Ole-Johan Dahl 创建的。

20 世纪 50 年代末,尼加德受雇于 挪威防务科学研究中心 Norwegian Defense Research Establishment (NDRE),该研究中心隶属于挪威军方。在那里,他负责设计 蒙特卡洛模拟方法 Monte Carlo simulations ,用于核反应堆设计与操作研究。最初,那些模拟实验是由人工完成的;后来,实验在 Ferranti Mercury 电脑 [1] 上编入程序运行。尼加德随后发现,将这些模拟实验输入电脑需要一种更有效的方式。

尼加德设计的这种模拟实验就是人们所知的“ 离散事件模型 discrete event model ”,这种模拟记录了一系列事件随着时间改变系统状态的进程。但是问题的关键在于模拟可以从一个事件跳跃到另一个事件中,因为事件是离散的,事件之间的系统不存在任何变化。根据尼加德和达尔在 1966 年发表的一篇关于 Simula 的论文,这种模型被迅速应用于“神经网络、通信系统、交通流量、生产系统、管理系统、社会系统等” [2] 领域的分析。因此,尼加德认为,其他人描述模拟实验时,可能也需要更高层级的模型。于是他开始物色人才,帮助他完成他称之为“ 模拟语言 Simulation Language ”或者“ 蒙特卡洛编译器 Monte Carlo Compiler ”的项目 [3]

达尔当时也受雇于挪威防务科学研究中心,专攻语言设计,此时也加入了尼加德的项目,扮演“沃兹尼亚克”的角色(LCTT 译注:指苹果公司联合创始人斯蒂夫·盖瑞·沃兹尼亚克)。在接下来一年左右的时间,尼加德和达尔携手开发了 Simula 0 语言。 [4] 这一语言的早期版本仅仅是在 ALGOL 60 基础上进行的较小拓展,当时也只是打算将其用作预处理程序而已。当时的语言要比后来的编程语言抽象得多,其基本语言结构是“ 车站 stations ”与“ 乘客 customers ”,这些结构可以用于针对具体某些离散事件网络建立模型。尼加德和达尔给出了一个模拟飞机离港的例子。 [5] 但是尼加德和达尔最后想出了一个更加通用的语言结构,可以同时表示“车站”和“乘客”,也可以为更广泛的模拟建立模型。这是两个主要的概括,它改变了 Simula 作为 ALGOL 专属包的定位,使其转变为通用编程语言。

Simula I 没有“ 车站 stations ”和“ 乘客 customers ”的语言结构,但它可以通过使用“ 进程 process ”再现这些结构。(LCTT 译注:此处使用的“进程”,与当前计算机中用来指代一个已执行程序的实体的概念不同,大致上,你可以将本文中所说的“进程”理解为一种“对象”。)一个进程包含大量数据属性,这些属性与作为进程的 操作规程 的单个行为相联系。你可能会把进程当作是只有单个方法的对象,比如 run() 之类的。不过,这种类比并不全面,因为每个进程的操作规程都可以随时暂停、随时恢复,因为这种操作规程属于 协程 coroutine 的一种。Simula I 程序会将系统建立为一套进程的模型,在概念上这些进程并行运行。实际上,一个时间点上能称为“当前进程”的只有一个进程。但是,一旦某个进程暂停运行,那么下一个进程就会自动接替它的位置。随着模拟的运行,Simula 会保持一个 “ 事件通知 event notices ” 的时间线,跟踪记录每个进程恢复的时间。为了恢复暂停运行的进程,Simula 需要记录多个 调用栈 call stacks 的情况。这就意味着 Simula 无法再作为 ALGOL 的预处理程序了,因为 ALGOL 只有一个 调用栈 call stacks 。于是,尼加德和达尔下定决心,开始编写自己的编译器。

尼加德和达尔在介绍该系统的论文中,借助图示,通过模拟一个可用机器数量有限的工厂,阐明了其用法。 [6] 在该案例中,进程就好比订单:通过寻找可用的机器,订单得以发出;如果没有可用的机器,订单就会搁置;而一旦有机器空出来,订单就会执行下去。有一个订单进程的定义,用来实例化若干种不同的订单实例,不过这些实例并未调用任何方法。该程序的主体仅仅是创建进程,并使其运行。

历史上第一个 Simula I 编译器发布于 1965 年。尼加德和达尔在离开挪威防务科学研究中心之后,就进入了 挪威计算机中心 Norwegian Computer Center 工作,Simula I 也是在这里日渐流行起来的。当时,Simula I 在 UNIVAC 公司的计算机和 Burroughs 公司的 B5500 计算机上均可执行。 [7] 尼加德和达尔两人与一家名为 ASEA 的瑞典公司达成了咨询协议,运用 Simula 模拟加工车间。但是,尼加德和达尔随后就意识到 Simula 也可以写一些和模拟完全不搭边的程序。

奥斯陆大学 University of Oslo 教授 斯坦因·克罗达尔 Stein Krogdahl 曾写过关于 Simula 的发展史,称“真正能够促使新开发的通用语言快速发展的催化剂”就是 一篇题为 《记录处理》 Record Handling 的论文,作者是英国计算机科学家 查尔斯·安东尼·理查德·霍尔 C.A.R. Hoare [8] 假如你现在读霍尔的这篇论文,你就不会怀疑这句话。当人们谈及面向对象语言的发展史时,一定会经常提起霍尔的大名。以下内容摘自霍尔的《记录处理》一文:

该方案设想,在程序执行期间,计算机内部存在任意数量的记录,每条记录都代表着程序员在过去、现在或未来所需的某个对象。程序对现有记录的数量保持动态控制,并可以根据当前任务的要求创建新的记录或删除现有记录。

计算机中的每条记录都必须属于数量有限但互不重合的记录类型中的一类;程序员可以根据需要声明尽可能多的记录类型,并借助标识符为各个类型命名。记录类型的命名可能是普通词汇,比如“牛”、“桌子”以及“房子”,同时,归属于这些类型的记录分别代表一头“牛”、一张“桌子”以及一座“房子”。

霍尔在这片论文中并未提到子类的概念,但是达尔由衷地感谢霍尔,是他引导了两人发现了这一概念。 [9] 尼加德和达尔注意到 Simula I 的进程通常具有相同的元素,所以引入父类来执行共同元素就会非常方便。这也强化了“进程”这一概念本身可以用作父类的可能性,也就是说,并非每种类型都必须用作只有单个操作规程的进程。这就是 Simula 语言迈向通用化的第二次飞跃,此时,Simula 67 真正成为了通用编程语言。正是如此变化让尼加德和达尔短暂地萌生了给 Simula 改名的想法,想让人们意识到 Simula 不仅仅可以用作模拟。 [10] 不过,考虑到 “Simula”这个名字的知名度已经很高了,另取名字恐怕会带来不小的麻烦。

1967 年,尼加德和达尔与 控制数据公司 Control Data 签署协议,着手开发Simula 的新版本:Simula 67。同年六月份的一场会议中,来自控制数据公司、奥斯陆大学以及挪威计算机中心的代表与尼加德和达尔两人会面,意在为这门新语言制定标准与规范。最终,会议发布了 《Simula 67 通用基础语言》,确定了该语言的发展方向。

Simula 67 编译器的开发由若干家供应商负责。 Simula 用户协会 The Association of Simula Users (ASU)也随后成立,并于每年举办年会。不久,Simula 67 的用户就遍及了 23 个国家。 [11]

21 世纪的 Simula 语言

人们至今还记得 Simula,是因为后来那些取代它的编程语言都受到了它的巨大影响。到了今天,你很难找到还在使用 Simula 写程序的人,但是这并不意味着 Simula 已经从这个世界上消失了。得益于 GNU cim,人们在今天依然能够编写和运行 Simula 程序。

cim 编译器遵循 1986 年修订后的 Simula 标准,基本上也就是 Simula 67 版本。你可以用它编写类、子类以及虚拟方法,就像是在使用 Simula 67 一样。所以,用 Python 或 Ruby 轻松写出短短几行面向对象的程序,你照样也可以用 cim 写出来:

! dogs.sim ;
Begin
    Class Dog;
        ! The cim compiler requires virtual procedures to be fully specified ;
        Virtual: Procedure bark Is Procedure bark;;
    Begin
        Procedure bark;
        Begin
            OutText("Woof!");
            OutImage;           ! Outputs a newline ;
        End;
    End;

    Dog Class Chihuahua;        ! Chihuahua is "prefixed" by Dog ;
    Begin
        Procedure bark;
        Begin
            OutText("Yap yap yap yap yap yap");
            OutImage;
        End;
    End;

    Ref (Dog) d;
    d :- new Chihuahua;         ! :- is the reference assignment operator ;
    d.bark;
End;

你可以按照下面代码执行程序的编译与运行:

$ cim dogs.sim
Compiling dogs.sim:
gcc -g -O2 -c dogs.c
gcc -g -O2 -o dogs dogs.o -L/usr/local/lib -lcim
$ ./dogs
Yap yap yap yap yap yap

(你可能会注意到,cim 先将 Simula 语言编译为 C 语言,然后传递给 C 语言编译器。)

这就是 1967 年的面向对象程序设计,除了语法方面的不同,和 2019 年的面向对象程序设计并无本质区别。如果你同意我的这一观点,你也就懂得了为什么人们会认为 Simula 在历史上是那么的重要。

不过,我更想介绍一下 Simula I 的核心概念——进程模型。Simula 67 保留了进程模型,不过只有在使用 Process 类 和 Simulation 块的时候才能调用。

为了表现出进程是如何运行的,我决定模拟下述场景。想象一下,有这么一座住满了村民的村庄,村庄的旁边有条小河边,小河里有很多的鱼。但是,村里的村民却只有一条鱼竿。村民们胃口很大,每隔一个小时就饿了。他们一饿,就会拿着鱼竿去钓鱼。如果一位村民正在等鱼竿,另一位村民自然也用不了。这样一来,村民们就会为了钓鱼排起长长的队伍。假如村民要等五、六分钟才能钓到一条鱼,那么这样等下去,村民们的身体状况就会变得越来越差。再假如,一位村民已经到了骨瘦如柴的地步,最后他可能就会饿死。

这个例子多少有些奇怪,虽然我也不说不出来为什么我脑袋里最先想到的是这样的故事,但是就这样吧。我们把村民们当作 Simula 的各个进程,观察在有着四个村民的村庄里,一天的模拟时间内会发生什么。

完整程序可以通过此处 GitHub Gist 的链接获取。

我把输出结果的最后几行放在了下面。我们来看看一天里最后几个小时发生了什么:

1299.45: 王五饿了,要了鱼竿。
1299.45: 王五正在钓鱼。
1311.39: 王五钓到了一条鱼。
1328.96: 赵六饿了,要了鱼竿。
1328.96: 赵六正在钓鱼。
1331.25: 李四饿了,要了鱼竿。
1340.44: 赵六钓到了一条鱼。
1340.44: 李四饿着肚子等着鱼竿。
1340.44: 李四在等鱼竿的时候饿死了。
1369.21: 王五饿了,要了鱼竿。
1369.21: 王五正在钓鱼。
1379.33: 王五钓到了一条鱼。
1409.59: 赵六饿了,要了鱼竿。
1409.59: 赵六正在钓鱼。
1419.98: 赵六钓到了一条鱼。
1427.53: 王五饿了,要了鱼竿。
1427.53: 王五正在钓鱼。
1437.52: 王五钓到了一条鱼。

可怜的李四最后饿死了,但是他比张三要长寿,因为张三还没到上午 7 点就饿死了。赵六和王五现在一定过得很好,因为需要鱼竿的就只剩下他们两个了。

这里,我要说明,这个程序最重要的部分只是创建了进程(四个村民),并让它们运行下去。各个进程操作对象(鱼竿)的方式与我们今天对对象的操作方式相同。但是程序的主体部分并没有调用任何方法,也没有修改进程的任何属性。进程本身具有内部状态,但是这种内部状态的改变只有进程自身才能做到。

在这个程序中,仍然有一些字段发生了变化,这类程序设计无法直接解决纯函数式编程所能解决的问题。但是正如克罗达尔所注意到的那样,“这一机制引导进行模拟的程序员为底层系统建立模型,生成一系列进程,每个进程表示了系统内的自然事件顺序。” [12] 我们不是主要从名词或行动者(对其他对象做事的对象)的角度来思考正在进行的进程。我们可以将程序的总控制权交予 Simula 的事件通知系统,克罗达尔称其为 “ 时间管理器 time manager ”。因此,尽管我们仍然在适当地改变进程,但是没有任何进程可以假设其他进程的状态。每个进程只能间接地与其他进程进行交互。

这种模式如何用以编写编译器、HTTP 服务器以及其他内容,尚且无法确定。(另外,如果你在 Unity 游戏引擎上编写过游戏,就会发现两者十分相似。)我也承认,尽管我们有了“时间管理器”,但这可能并不完全是希基的意思,他说我们在程序中需要一个明确的时间概念。(我认为,希基想要的类似于 阿达·洛芙莱斯 Ada Lovelace 用于区分一个变量随时间变化产生的不同数值的上标符号。)尽管如此,我们可以发现,面向对象程序设计前期的设计方式与我们今天所习惯的面向对象程序设计并非完全一致,我觉得这一点很有意思。我们可能会理所当然地认为,面向对象程序设计的方式千篇一律,即程序就是对事件的一长串记录:某个对象以特定顺序对其他对象产生作用。Simula I 的进程系统表明,面向对象程序设计的方式不止一种。仔细想一下,函数式语言或许是更好的设计方式,但是 Simula I 的发展告诉我们,现代面向对象程序设计被取代也很正常。

如果你喜欢这篇文章,欢迎关注推特 @TwoBitHistory,也可通过 RSS feed 订阅,获取最新文章(每四周更新一篇)。


  1. Jan Rune Holmevik, “The History of Simula,” accessed January 31, 2019, http://campus.hesge.ch/daehne/2004-2005/langages/simula.htm. ↩︎
  2. Ole-Johan Dahl and Kristen Nygaard, “SIMULA—An ALGOL-Based Simulation Langauge,” Communications of the ACM 9, no. 9 (September 1966): 671, accessed January 31, 2019, http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.95.384&rep=rep1&type=pdf. ↩︎
  3. Stein Krogdahl, “The Birth of Simula,” 2, accessed January 31, 2019, http://heim.ifi.uio.no/~steinkr/papers/HiNC1-webversion-simula.pdf. ↩︎
  4. 出处同上。 ↩︎
  5. Ole-Johan Dahl and Kristen Nygaard, “The Development of the Simula Languages,” ACM SIGPLAN Notices 13, no. 8 (August 1978): 248, accessed January 31, 2019, https://hannemyr.com/cache/knojd_acm78.pdf. ↩︎
  6. Dahl and Nygaard (1966), 676. ↩︎
  7. Dahl and Nygaard (1978), 257. ↩︎
  8. Krogdahl, 3. ↩︎
  9. Ole-Johan Dahl, “The Birth of Object-Orientation: The Simula Languages,” 3, accessed January 31, 2019, http://www.olejohandahl.info/old/birth-of-oo.pdf. ↩︎
  10. Dahl and Nygaard (1978), 265. ↩︎
  11. Holmevik. ↩︎
  12. Krogdahl, 4. ↩︎

via: https://twobithistory.org/2019/01/31/simula.html

作者:Two-Bit History 选题:lujun9972 译者:aREversez 校对:校对者ID

本文由 LCTT 原创编译,Linux中国 荣誉推出

ARPANET 通过证明可以使用标准化协议连接完全不同的制造商的计算机,永远改变了计算。在我的 关于 ARPANET 的历史意义的文章 中,我提到了其中的一些协议,但没有详细描述它们。所以我想仔细看看它们。也想看看那些早期协议的设计有多少保留到了我们今天使用的协议中。

ARPANET 协议像我们现代的互联网协议,是通过分层形式来组织的。 [1] 较高层协议运行在较低层协议之上。如今的 TCP/IP 套件有 5 层(物理层、链路层、网络层、传输层以及应用层),但是这个 ARPANET 仅有 3 层,也可能是 4 层,这取决于你怎样计算它们。

我将会解释每一层是如何工作的,但首先,你需要知道是谁在 ARPANET 中构建了些什么,你需要知道这一点才能理解为什么这些层是这样划分的。

一些简短的历史背景

ARPANET 由美国联邦政府资助,确切的说是位于美国国防部的 高级研究计划局 Advanced Research Projects Agency (因此被命名为 “ARPANET” )。美国政府并没有直接建设这个网络;而是,把这项工作外包给了位于波士顿的一家名为 “Bolt, Beranek, and Newman” 的咨询公司,通常更多时候被称为 BBN。

而 BBN 则承担了实现这个网络的大部分任务,但不是全部。BBN 所做的是设计和维护一种称为 接口消息处理机 Interface Message Processor (简称为 IMP) 的机器。这个 IMP 是一种定制的 霍尼韦尔 Honeywell 小型机 minicomputer ,它们被分配给那些想要接入这个 ARPANET 的遍及全国各地的各个站点。它们充当通往 ARPANET 的网关,为每个站点提供多达四台主机的连接支持。它基本上是一台路由器。BBN 控制在 IMP 上运行的软件,把数据包从一个 IMP 转发到另一个 IMP ,但是该公司无法直接控制那些将要连接到 IMP 上并且成为 ARPANET 网络中实际主机的机器。

那些主机由网络中作为终端用户的计算机科学家们所控制。这些计算机科学家在全国各地的主机站负责编写软件,使主机之间能够相互通讯。而 IMP 赋予主机之间互相发送消息的能力,但是那并没有多大用处,除非主机之间能商定一种用于消息的格式。为了解决这个问题,一群杂七杂八的人员组成了网络工作组,其中有大部分是来自各个站点的研究生们,该组力求规定主机计算机使用的协议。

因此,如果你设想通过 ARPANET 进行一次成功的网络互动,(例如发送一封电子邮件),使这些互动成功的一些工程由一组人负责(BBN),然而其他的一些工程则由另一组人负责(网络工作组和在每个站点的工程师们)。这种组织和后勤方面的偶然性或许对推动采用分层的方法来管理 ARPANET 网络中的协议起到很大的作用,这反过来又影响了 TCP/IP 的分层方式。

好的,回到协议上来

ARPANET Network Stack

ARPANET 协议层次结构

这些协议层被组织成一个层次结构,在最底部是 “Level 0”。 [2] 这在某种意义上是不算数的,因为在 ARPANET 中这层完全由 BBN 控制,所以不需要标准协议。Level 0 的作用是管理数据在 IMP 之间如何传输。在 BBN 内部,有管理 IMP 如何做到这一点的规则;在 BBN 之外,IMP 子网是一个黑匣子,它只会传送你提供的任意数据。因此,Level 0 是一个没有真正协议的层,就公开已知和商定的规则集而言,它的存在可以被运行在 ARPANET 的主机上的软件忽略。粗略地说,它处理相当于当今使用的 TCP/IP 套件的物理层、链路层和网络层下的所有内容,甚至还包括相当多的传输层,这是我将在这篇文章的末尾回来讨论的内容。

“Level 1” 层在 ARPANET 的主机和它们所连接的 IMP 之间建立了接口。如果你愿意,可以认为它是为 BBN 构建的 “Level 0” 层的黑匣子使用的一个应用程序接口(API)。当时它也被称为 IMP-Host 协议。必须编写该协议并公布出来,因为在首次建立 ARPANET 网络时,每个主机站点都必须编写自己的软件来与 IMP 连接。除非 BBN 给他们一些指导,否则他们不会知道如何做到这一点。

BBN 在一份名为 BBN Report 1822 的冗长文件中规定了 IMP-Host 协议。随着 ARPANET 的发展,该文件多次被修订;我将在这里大致描述 IMP-Host 协议最初设计时的工作方式。根据 BBN 的规则,主机可以将长度不超过 8095 位的消息传递给它们的 IMP,并且每条消息都有一个包含目标主机号和链路识别号的头部字段。 [3] IMP 将检查指定的主机号,然后尽职尽责地将消息转发到网络中。当从远端主机接收到消息时,接收的 IMP 在将消息传递给本地主机之前会把目标主机号替换为源主机号。实际上在 IMP 之间传递的内容并不是消息 —— IMP 将消息分解成更小的数据包以便通过网络传输 —— 但该细节对主机来说是不可见的。

1969 Host-IMP Leader

Host-IMP 消息头部格式,截至 1969。 图表来自 BBN Report 1763

链路号的取值范围为 0 到 255 ,它有两个作用。一是更高级别的协议可以利用它在网络上的任何两台主机之间建立多个通信信道,因为可以想象得到,在任何时刻都有可能存在多个本地用户与同一个目标主机进行通信的场景(换句话说,链路号允许在主机之间进行多路通信)。二是它也被用在 “Level 1” 层去控制主机之间发送的大量流量,以防止高性能计算机压制低性能计算机的情况出现。按照最初的设计,这个 IMP-Host 协议限制每台主机在某一时刻通过某条链路仅发送一条消息。一旦某台主机沿着某条链路发送了一条消息给远端主机后,在它沿着该链路发送下一条消息之前,必须等待接收一条来自远端的 IMP 的特别类型的消息,叫做 RFNM( 请求下一条消息 Request for Next Message )。后来为了提高性能,对该系统进行了修订,允许一台主机在给定的时刻传送多达 8 条消息给另一台主机。 [4]

“Level 2” 层才是事情真正开始变得有趣的地方,因为这一层和在它上面的那一层由 BBN 和国防部全部留给学者们和网络工作组自己去研发。“Level 2” 层包括了 Host-Host 协议,这个协议最初在 RFC9 中草拟,并且在 RFC54 中首次正式规定。在 ARPANET 协议手册 中有更易读的 Host-Host 协议的解释。

“Host-Host 协议” 管理主机之间如何创建和管理连接。“连接”是某个主机上的写套接字和另一个主机上的读套接字之间的一个单向的数据管道。“ 套接字 socket ” 的概念是在 “Level-1” 层的有限的链路设施(记住,链路号只能是那 256 个值中的一个)之上被引入的,是为了给程序提供寻址运行在远端主机上的特定进程的一种方式。“读套接字” 是用偶数表示的,而“写套接字”是用奇数表示的;套接字是 “读” 还是 “写” 被称为套接字的 “性别”。并没有类似于 TCP 协议那样的 “端口号” 机制,连接的打开、维持以及关闭操作是通过主机之间使用 “链路 0” 发送指定格式的 Host-Host 控制消息来实现的,这也是 “链路 0” 被保留的目的。一旦在 “链路 0” 上交换控制消息来建立起一个连接后,就可以使用接收端挑选的另一个链路号来发送进一步的数据消息。

Host-Host 控制消息一般通过 3 个字母的助记符来表示。当两个主机交换一条 STR( 发送端到接收端 sender-to-receiver )消息和一条配对的 RTS( 接收端到发送端 receiver-to-sender )消息后,就建立起了一条连接 —— 这些控制消息都被称为请求链接消息。链接能够被 CLS( 关闭 close )控制消息关闭。还有更多的控制信息能够改变从发送端到接收端发送消息的速率。从而再次需要确保较快的主机不会压制较慢的主机。在 “Level 1” 层上的协议提供了流量控制的功能,但对 “Level 2” 层来说显然是不够的;我怀疑这是因为从远端 IMP 接收到的 RFNM 只能保证远端 IMP 已经传送该消息到目标主机,而不能保证目标主机已经全部处理了该消息。还有 INR( 接收端中断 interrupt-by-receiver )、INS( 发送端中断 interrupt-by-sender )控制消息,主要供更高级别的协议使用。

更高级别的协议都位于 “Level 3”,这层是 ARPANET 的应用层。Telnet 协议,它提供到另一台主机的一个虚拟电传链接,其可能是这些协议中最重要的。但在这层中也有许多其他协议,例如用于传输文件的 FTP 协议和各种用于发送 Email 的协议实验。

在这一层中有一个不同于其他的协议: 初始链接协议 Initial Connection Protocol (ICP)。ICP 被认为是一个 “Level-3” 层协议,但实际上它是一种 “Level-2.5” 层协议,因为其他 “Level-3” 层协议都依赖它。之所以需要 ICP,是因为 “Level 2” 层的 Host-Host 协议提供的链接只是单向的,但大多数的应用需要一个双向(例如:全双工)的连接来做任何有趣的事情。要使得运行在某个主机上的客户端能够连接到另一个主机上的长期运行的服务进程,ICP 定义了两个步骤。第一步是建立一个从服务端到客户端的单向连接,通过使用服务端进程的众所周知的套接字号来实现。第二步服务端通过建立的这个连接发送一个新的套接字套接字号给客户端。到那时,那个存在的连接就会被丢弃,然后会打开另外两个新的连接,它们是基于传输的套接字号建立的“读”连接和基于传输的套接字号加 1 的“写”连接。这个小插曲是大多数事务的一个前提——比如它是建立 Telnet 链接的第一步。

以上是我们逐层攀登了 ARPANET 协议层次结构。你们可能一直期待我在某个时候提一下 “ 网络控制协议 Network Control Protocol ”(NCP) 。在我坐下来为这篇文章和上一篇文章做研究之前,我肯定认为 ARPANET 运行在一个叫 “NCP” 的协议之上。这个缩写有时用来指代整个 ARPANET 协议,这可能就是我为什么有这个想法的原因。举个例子,RFC801 讨论了将 ARPANET 从 “NCP” 过渡到 “TCP” 的方式,这使 NCP 听起来像是一个相当于 TCP 的 ARPANET 协议。但是对于 ARPANET 来说,从来都没有一个叫 “网络控制协议” 的东西(即使 大英百科全书是这样认为的),我怀疑人们错误地将 “NCP” 解释为 “ 网络控制协议 Network Control Protocol ” ,而实际上它代表的是 “ 网络控制程序 Network Control Program ” 。网络控制程序是一个运行在各个主机上的内核级别的程序,主要负责处理网络通信,等同于现如今操作系统中的 TCP/IP 协议栈。用在 RFC 801 的 “NCP” 是一种转喻,而不是协议。

与 TCP/IP 的比较

ARPANET 协议以后都会被 TCP/IP 协议替换(但 Telnet 和 FTP 协议除外,因为它们很容易就能在 TCP 上适配运行)。然而 ARPANET 协议都基于这么一个假设:就是网络是由一个单一实体(BBN)来构建和管理的。而 TCP/IP 协议套件是为网间网设计的,这是一个网络的网络,在那里一切都是不稳定的和不可靠的。这就导致了我们的现代协议套件和 ARPANET 协议有明显的不同,比如我们现在怎样区分网络层和传输层。在 ARPANET 中部分由 IMP 实现的类似传输层的功能现在完全由在网络边界的主机负责。

我发现 ARPANET 协议最有趣的事情是,现在在 TCP 中的许多传输层的功能是如何在 ARPANET 上经历了一个糟糕的青春期。我不是网络专家,因此我拿出大学时的网络课本(让我们跟着 Kurose 和 Ross 学习一下),他们对传输层通常负责什么给出了一个非常好的概述。总结一下他们的解释,一个传输层协议必须至少做到以下几点。这里的 “ segment ” 基本等同于 ARPANET 上的术语 “ 消息 message ”:

  • 提供进程之间的传送服务,而不仅仅是主机之间的(传输层多路复用和多路分解)
  • 在每个段的基础上提供完整性检查(即确保传输过程中没有数据损坏)

像 TCP 那样,传输层也能够提供可靠的数据传输,这意味着:

  • “段” 是按顺序被传送的
  • 不会丢失任何 “段”
  • “段” 的传送速度不会太快以至于被接收端丢弃(流量控制)

似乎在 ARPANET 上关于如何进行多路复用和多路分解以便进程可以通信存在一些混淆 —— BBN 在 IMP-Host 层引入了链路号来做到这一点,但结果证明在 Host-Host 层上无论如何套接字号都是必要的。然后链路号只是用于 IMP-Host 级别的流量控制,但 BBN 似乎后来放弃了它,转而支持在唯一的主机对之间进行流量控制,这意味着链路号一开始是一个超载的东西,后来基本上变成了虚设。TCP 现在使用端口号代替,分别对每一个 TCP 连接单独进行流量控制。进程间的多路复用和多路分解完全在 TCP 内部进行,不会像 ARPANET 一样泄露到较低层去。

同样有趣的是,鉴于 Kurose 和 Ross 如何开发 TCP 背后的想法,ARPANET 一开始就采用了 Kurose 和 Ross 所说的一个严谨的 “ 停止并等待 stop-and-wait ” 方法,来实现 IMP-Host 层上的可靠的数据传输。这个 “停止并等待” 方法发送一个 “段” 然后就拒绝再去发送更多 “段” ,直到收到一个最近发送的 “段” 的确认为止。这是一种简单的方法,但这意味着只有一个 “段” 在整个网络中运行,从而导致协议非常缓慢 —— 这就是为什么 Kurose 和 Ross 将 “停止并等待” 仅仅作为在通往功能齐全的传输层协议的路上的垫脚石的原因。曾有一段时间 “停止并等待” 是 ARPANET 上的工作方式,因为在 IMP–Host 层,必须接收到 请求下一条消息 Request for Next Message (RFNM)以响应每条发出的消息,然后才能发送任何进一步的消息。客观的说 ,BBN 起初认为这对于提供主机之间的流量控制是必要的,因此减速是故意的。正如我已经提到的,为了更好的性能,RFNM 的要求后来放宽松了,而且 IMP 也开始向消息中添加序列号和保持对传输中的消息的 “窗口” 的跟踪,这或多或少与如今 TCP 的实现如出一辙。 [5]

因此,ARPANET 表明,如果你能让每个人都遵守一些基本规则,异构计算系统之间的通信是可能的。正如我先前所说的,这是 ARPANET 的最重要的遗产。但是,我希望对这些基线规则的仔细研究揭示了 ARPANET 协议对我们今天使用的协议有多大影响。在主机和 IMP 之间分担传输层职责的方式上肯定有很多笨拙之处,有时候是冗余的。现在回想起来真的很可笑,主机之间一开始只能通过给出的任意链路在某刻只发送一条消息。但是 ARPANET 实验是一个独特的机会,可以通过实际构建和操作网络来学习这些经验,当到了是时候升级到我们今天所知的互联网时,似乎这些经验变得很有用。

如果你喜欢这篇贴子,更喜欢每四周发布一次的方式!那么在 Twitter 上关注 @TwoBitHistory 或者订阅 RSS 提要,以确保你知道新帖子的发布时间。


  1. 协议分层是网络工作组发明的。这个论点是在 RFC 871 中提出的。分层也是 BBN 如何在主机和 IMP 之间划分职责的自然延伸,因此 BBN 也值得称赞。 ↩︎
  2. “level” 是被网络工作组使用的术语。 详见 RFC 100 ↩︎
  3. 在 IMP-Host 协议的后续版本中,扩展了头部字段,并且将链路号升级为消息 ID。但是 Host-Host 协议仅仅继续使用消息 ID 字段的高位 8 位,并将其视为链路号。请参阅 ARPANET 协议手册 的 “Host-Host” 协议部分。 ↩︎
  4. John M. McQuillan 和 David C. Walden。 “ARPA 网络设计决策”,第 284页,https://www.walden-family.com/public/whole-paper.pdf。 2021 年 3 月 8 日查看。 ↩︎
  5. 同上。 ↩︎

via: https://twobithistory.org/2021/03/08/arpanet-protocols.html

作者:Two-Bit History 选题:lujun9972 译者:Lin-vy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

有时我会觉得自己的计算机是一栋非常大的房子,我每天都会访问这栋房子,也对一楼的大部分房间都了如指掌,但仍然还是有我没有去过的卧室,有我没有打开过的衣柜,有我没有探索过的犄角旮旯。我感到有必要更多地了解我的计算机了,就像任何人都会觉得有必要看看自己家里从未去过的房间一样。

GNU Readline 是个不起眼的小软件库,我依赖了它多年却没有意识到它的存在,也许有成千上万的人每天都在不经意间使用它。如果你用 Bash shell 的话,每当你自动补全一个文件名,或者在输入的一行文本中移动光标,以及搜索之前命令的历史记录时,你都在使用 GNU Readline;当你在 Postgres(psql)或是 Ruby REPL(irb)的命令行界面中进行同样的操作时,你依然在使用 GNU Readline。很多软件都依赖 GNU Readline 库来实现用户所期望的功能,不过这些功能是如此的辅助与不显眼,以至于在我看来很少有人会停下来去想它是从哪里来的。

GNU Readline 最初是自由软件基金会在 20 世纪 80 年代创建的,如今作为每个人的基础计算设施的重要的、甚至看不见的组成部分的它,由一位志愿者维护。

充满特色

GNU Readline 库的存在,主要是为了增强各种命令行界面,它提供了一组通用的按键,使你可以在一个单行输入中移动和编辑。例如,在 Bash 提示符中按下 Ctrl-A,你的光标会跳到行首,而按下 Ctrl-E 则会跳到行末;另一个有用的命令是 Ctrl-U,它会删除该行中光标之前的所有内容。

有很长一段时间,我通过反复敲击方向键来在命令行上移动,如今看来这十分尴尬,也不知道为什么,当时的我从来没有想过可以有一种更快的方法。当然了,没有哪一个熟悉 Vim 或 Emacs 这种文本编辑器的程序员愿意长时间地击打方向键,所以像 Readline 这样的东西必然会被创造出来。在 Readline 上可以做的绝非仅仅跳来跳去,你可以像使用文本编辑器那样编辑单行文本——这里有删除单词、单词换位、大写单词、复制和粘贴字符等命令。Readline 的大部分按键/快捷键都是基于 Emacs 的,它基本上就是一个单行文本版的 Emacs 了,甚至还有录制和重放宏的功能。

我从来没有用过 Emacs,所以很难记住所有不同的 Readline 命令。不过 Readline 有着很巧妙的一点,那就是能够切换到基于 Vim 的模式,在 Bash 中可以使用内置的 set 命令来这样做。下面会让 Readline 在当前的 shell 中使用 Vim 风格的命令:

$ set -o vi

该选项启用后,就可以使用 dw 等命令来删除单词了,此时相当于 Emacs 模式下的 Ctrl-U 的命令是 d0

我第一次知道有这个功能的时候很兴奋地想尝试一下,但它对我来说并不是那么好用。我很高兴知道有这种对 Vim 用户的让步,在使用这个功能上你可能会比我更幸运,尤其是你还没有使用 Readline 的默认按键的话;我的问题在于,我听说有基于 Vim 的界面时已经学会了几种默认按键,因此即使启用了 Vim 的选项,也一直在错误地用着默认的按键;另外因为没有某种指示器,所以 Vim 的模态设计在这里会很尴尬——你很容易就忘记了自己处于哪个模式,就因为这样,我卡在了一种虽然使用 Vim 作为文本编辑器,但却在 Readline 上用着 Emacs 风格的命令的情况里,我猜其他很多人也是这样的。

如果你觉得 Vim 和 Emacs 的键盘命令系统诡异而神秘(这并不是没有道理的),你可以按照喜欢的方式自定义 Readline 的键绑定。Readline 在启动时会读取文件 ~/.inputrc,它可以用来配置各种选项与键绑定,我做的一件事是重新配置了 Ctrl-K:通常情况下该命令会从光标处删除到行末,但我很少这样做,所以我在 ~/.inputrc 中添加了以下内容,把它绑定为直接删除整行:

Control-k: kill-whole-line

每个 Readline 命令(文档中称它们为 “函数” )都有一个名称,你可以用这种方式将其与一个键序列联系起来。如果你在 Vim 中编辑 ~/.inputrc,就会发现 Vim 知道这种文件类型,还会帮你高亮显示有效的函数名,而不高亮无效的函数名。

~/.inputrc 可以做的另一件事是通过将键序列映射到输入字符串上来创建预制宏。Readline 手册给出了一个我认为特别有用的例子:我经常想把一个程序的输出保存到文件中,这意味着我得经常在 Bash 命令中追加类似 > output.txt 这样的东西,为了节省时间,可以把它做成一个 Readline 宏:

Control-o: "> output.txt"

这样每当你按下 Ctrl-O 时,你都会看到 > output.txt 被添加到了命令行光标的后面,这样很不错!

不过你可以用宏做的可不仅仅是为文本串创建快捷方式;在 ~/.inputrc 中使用以下条目意味着每次按下 Ctrl-J 时,行内已有的文本都会被 $() 包裹住。该宏先用 Ctrl-A 移动到行首,添加 $( ,然后再用 Ctrl-E 移动到行尾,添加 )

Control-j: "\C-a$(\C-e)"

如果你经常需要像下面这样把一个命令的输出用于另一个命令的话,这个宏可能会对你有帮助:

$ cd $(brew --prefix)

~/.inputrc 文件也允许你为 Readline 手册中所谓的 “变量” 设置不同的值,这些变量会启用或禁用某些 Readline 行为,你也可以使用这些变量来改变 Readline 中像是自动补全或者历史搜索这些行为的工作方式。我建议开启的一个变量是 revert-all-at-newline,它是默认关闭的,当这个变量关闭时,如果你使用反向搜索功能从命令历史记录中提取一行并编辑,但随后又决定搜索另一行,那么你所做的编辑会被保存在历史记录中。我觉得这样会很混乱,因为这会导致你的 Bash 命令历史中出现从未运行过的行。所以在你的 ~/.inputrc 中加入这个:

set revert-all-at-newline on

在你用 ~/.inputrc 设置了选项或键绑定以后,它们会适用于任何使用 Readline 库的地方,显然 Bash 也包括在内,不过你也会在其它像是 irbpsql 这样的程序中受益。如果你经常使用关系型数据库的命令行界面,一个用于插入 SELECT * FROM 的 Readline 宏可能会很有用。

Chet Ramey

GNU Readline 如今由凯斯西储大学的高级技术架构师 Chet Ramey 维护,Ramey 同时还负责维护 Bash shell;这两个项目都是由一位名叫 Brian Fox 的自由软件基金会员工在 1988 年开始编写的,但从 1994 年左右开始,Ramey 一直是它们唯一的维护者。

Ramey 通过电子邮件告诉我,Readline 远非一个原创的想法,它是为了实现 POSIX 规范所规定的功能而被创建的,而 POSIX 规范又是在 20 世纪 80 年代末被制定的。许多早期的 shell,包括 Korn shell 和至少一个版本的 Unix System V shell,都包含行编辑功能。1988 年版的 Korn shell(ksh88)提供了 Emacs 风格和 Vi/Vim 风格的编辑模式。据我从手册页中得知,Korn shell 会通过查看 VISUALEDITOR 环境变量来决定你使用的模式,这一点非常巧妙。POSIX 中指定 shell 功能的部分近似于 ksh88 的实现,所以 GNU Bash 也要实现一个类似的灵活的行编辑系统来保持兼容,因此就有了 Readline。

Ramey 第一次参与 Bash 开发时,Readline 还是 Bash 项目目录下的一个单一的源文件,它其实只是 Bash 的一部分;随着时间的推移,Readline 文件慢慢地成为了独立的项目,不过直到 1994 年(Readline 2.0 版本发布),Readline 才完全成为了一个独立的库。

Readline 与 Bash 密切相关,Ramey 也通常把 Readline 与 Bash 的发布配对,但正如我上面提到的,Readline 是一个可以被任何有命令行界面的软件使用的库,而且它真的很容易使用。下面是一个例子,虽然简单,但这就是在 C 程序中使用 Readline 的方法。向 readline() 函数传递的字符串参数就是你希望 Readline 向用户显示的提示符:

#include <stdio.h>
#include <stdlib.h>
#include "readline/readline.h"

int main(int argc, char** argv)
{
    char* line = readline("my-rl-example> ");
    printf("You entered: \"%s\"\n", line);

    free(line);

    return 0;
}

你的程序会把控制权交给 Readline,它会负责从用户那里获得一行输入(以这样的方式让用户可以做所有花哨的行编辑工作),一旦用户真正提交了这一行,Readline 就会把它返回给你。在我的库搜索路径中有 Readline 库,所以我可以通过调用以下内容来链接 Readline 库,从而编译上面的内容:

$ gcc main.c -lreadline

当然,Readline 的 API 比起那个单一的函数要丰富得多,任何使用它的人都可以对库的行为进行各种调整,库的用户(开发者)甚至可以添加新的函数,来让最终用户可以通过 ~/.inputrc 来配置它们,这意味着 Readline 非常容易扩展。但是据我所知,即使是 Bash ,虽然事先有很多配置,最终也会像上面的例子一样调用简单的 readline() 函数来获取输入。(参见 GNU Bash 源代码中的这一行,Bash 似乎在这里将获取输入的责任交给了 Readline)。

Ramey 现在已经在 Bash 和 Readline 上工作了二十多年,但他的工作却从来没有得到过报酬 —— 他一直都是一名志愿者。Bash 和 Readline 仍然在积极开发中,尽管 Ramey 说 Readline 的变化比 Bash 慢得多。我问 Ramey 作为这么多人使用的软件唯一的维护者是什么感觉,他说可能有几百万人在不知不觉中使用 Bash(因为每个苹果设备都运行 Bash),这让他担心一个破坏性的变化会造成多大的混乱,不过他已经慢慢习惯了所有这些人的想法。他还说他会继续在 Bash 和 Readline 上工作,因为在这一点上他已经深深地投入了,而且他也只是单纯地喜欢把有用的软件提供给世界。

你可以在 Chet Ramey 的网站上找到更多关于他的信息。

喜欢这篇文章吗?我会每四周写出一篇像这样的文章。关注推特帐号 @TwoBitHistory 或者订阅 RSS 来获取更新吧!


via: https://twobithistory.org/2019/08/22/readline.html

作者:Two-Bit History 选题:lujun9972 译者:rakino 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

1983 年时我还没出生,这让我略有一些遗憾。但我特别遗憾的是,是我没有经历过 8 位计算机时代的到来,因为我认为那些初次接触到还相对简单和受限的计算机的人们,拥有超过如今的我们的巨大优势。

今天,(几乎)每个人知道如何使用计算机,但是即使是在计算机行业当中,也很少有人能明白任何一台计算机内部的所有内容。现在软件分为如此多的层次,做的是如此不同的事情,没有哪个人能知道哪些是必不可少的。而在 1983 年,家用电脑足够傻大粗,努力一些的人就能了解到一台特定的计算机是如何工作的。那样的一个人可能不会像今天的我觉得操作系统那么神秘,因为如今的操作系统已经在硬件上叠加了太多的抽象层。我希望这些抽象层逐一引入以易于理解;而今天,新的程序员们不得不自上而下、按时间回溯地尝试理解它们。

很多著名的程序员,尤其在计算机游戏行业,孩童时期就在苹果 II 和 Commodore 64 这样的 8 位计算机上开始编写游戏,John Romero、Richard Garriott 和 Chris Roberts 都是这样的例子。这好理解。在 8 位计算机时代,很多游戏只能在计算机杂志和书籍中以印刷的 BASIC 程序清单方式得到。如果你想玩其中一款游戏,就必须手工键入整个程序。不可避免的,你可能遇到一些问题,所以你就得调试你的程序。等到你让它可以工作起来了,你就已经对程序是如何运行的有了足够的了解,你就可以开始自己修改它了。如果你是一个狂热的游戏玩家,你几乎必然会成为一名优秀的程序员。

在我的童年时期我也玩电脑游戏。但是我玩的游戏是在 CD-ROM 上的。我有时发现我自己必须得搜索一下如何修复崩溃的安装程序,这可能涉及编辑 Windows 注册表之类的东西。可能是这种小故障的排除让我感觉很棒,所以我才考虑在大学里学习计算机科学。但是在大学中从不教我一些计算机如何工作的或如何控制它们的关键性的东西。

当然,现在我可以告诉计算机去干什么。尽管如此,我还是不禁感到,我缺少一些根本的见解 —— 只有那些伴随着更简单的计算机编程而成长的人才有的深刻见解。我不禁在想,如果在上世纪 80 年代初就接触到计算机会是什么样子?它们与今天使用计算机的体验相比有何不同?

这篇文章将与通常的 Two-Bit History 的文章有一点不同,因为我将为这些问题尝试设想一个答案。

1983

就在上周,你在电视上看到了 Commodore 64 的广告 ,现在 M*A*S*H 播完了(LCTT 译注: 这是一部上世纪 70 年代初的电视剧),星期一晚上你可以找点新的事情做了。这个 Commodore 64 甚至看起来比鲁迪(LCTT 译注:应该是下文中拥有 Apple II 的人)的家人放在他们家地下室的 Apple II 更好。而且,广告中吹嘘说新的计算机会让你的朋友们“挤破”你家的大门。你知道学校里的几个家伙宁愿在你家闲逛,也不愿去鲁迪家里,只要他们能玩 Zork 就行。

所以,你得说服你的父母去买一台。你的母亲说,这事可以考虑,只要你不去游戏厅玩街机就给你买一台家庭电脑。虽然不太情愿,但是你还是同意了。而你的父亲则想,他可以用 MultiPlan (LCTT 译注:电子表格程序)跟踪家庭的资金状况,MultiPlan 是他曾听说过的一个电子表格程序,这就是为什么这台计算机被放在客厅的原因。然而,一年后,你仍然是唯一使用它的人。最终,他们同意你把它搬到了你的卧室的桌子上,正好位于你的警察海报下方。

(你的姐姐对这个决定表示抗议,但是,在 1983 年电脑这种东西并不适合女孩。)

你的父亲在下班路上从 ComputerLand 那里把它捎了回来。你俩把盒子放置在电视机的旁边,并打开了它。外包装上说“欢迎来到友好的计算机世界”。而二十分钟以后你就不再信这句话了 —— 你俩仍然在尝试把 Commodore 连接到电视机上,并在想电视机的天线电缆到底是 75 欧姆还是 300 欧姆的同轴电缆。但是,最终你把电视机调到了频道 3,看到了一个颗粒状的、紫色的图像。

Commodore 64 启动屏幕

计算机显示了一个 READY。你的爸爸把计算机推向了你,这意思是你是第一个尝试它的人。你小心翼翼地敲击每个字母,键入了 HELLO。然而计算机的回应是令人困惑的。

Commodore 64 语法错误

你尝试输入了一些稍有不同的单词,然而回应总是一样的。你父亲说,你最好仔细读一下手册的其它部分。这绝非易事,随 Commodore 64 一起提供的手册 是一本小一些的书。但是这不会困住你,因为手册的介绍预示着奇迹。

它声称,Commodore 64 有“微型计算机行业中最先进的图画制作器”,能允许“你设计拥有四种不同颜色的图画,就像你在街机视频游戏里看到的一样”。Commodore 64 也有“内置的音乐和声音效果,可以与很多著名的音乐合成器相媲美”。所有的这些工具都置身于你的手边,手册会引导你完成所有这些:

与所有提供的硬件一样重要的是,这本用户指南将提高你对计算机的理解。它无法在这里告诉你有关计算机的所有信息,但是它会向你推荐各种出版物,以获取有关所提出主题的更多详细信息。Commodore 希望你真正喜欢你的新 COMMODORE 64。要想真正得到乐趣,请记住:编程不是一种一天就能学会的东西。通读这个用户指南你要有耐心。

那一夜,你在床上通读了整整前三个章节:“安装”、“入门”和“BASIC 编程入门”,在你最终睡着时,手册还打开着放在了胸前。

Commodore BASIC

现在是星期六早上,你渴望尝试你所学到的新东西。手册里教给你的第一件事是如何更改在显示器上的颜色。你按照操作说明,按下 CTRL-9 来进入反色输入模式,然后按住空格键来创建了一个长长的空行。你可以使用 CTRL-1CTRL-8 在不同的颜色之间交换,这让你的电视机屏幕焕发出了新的力量。

Commodore 64 颜色带

尽管这很酷,但你觉得这不能算是编程。要对计算机编程,你昨晚已经学会了如何做,你必须以一种称为 BASIC 的语言与计算机交谈。对你来说,BASIC 看起来就像星球大战中的东西一样科幻,但是,到 1983 年时,其实 BASIC 已经快有二十岁了。它是由两位达特茅斯教授 John Kemeny 和 Tom Kurtz 发明的,他们想让社会科学和人文科学中的本科生也可以使用计算机。它被广泛使用在微型计算机上,在大学的数学课上很受欢迎。在比尔盖茨和保罗艾伦为 Altair 编写了微软 BASIC 解释器后,它就成为了微型计算机上的标准。但是这本手册对此没有任何解释,那么多年你都没学过它。

手册中建议你尝试的第一个 BASIC 命令是 PRINT 命令。你输入了 PRINT "COMMODORE 64",很慢,因为你需要花费一点时间才能在按键 2 上面找到引号符号。你单击 RETURN,这一次,计算机没有抱怨,完全是按照你告诉它做的,在下一行中显示了 “COMMODORE 64” 。

现在你尝试对各种不同的东西使用 PRINT 命令:两个数字加在一起,两个数字乘在一起,甚至几个十进制数字。你不再输入 PRINT ,而是使用 ? 代替,因为手册中告诉你 ?PRINT 的一个缩写,通常专业程序员都这么使用。你感觉自己已经像是一个专家了,不过你想起你还没有进行到第三章“BASIC 编程入门”。

你很快就开始了。该章节提示你编写你的第一个真正的 BASIC 程序。你输入 NEW 并单击 RETURN,它给了你一个干净的 黑板 slate 。然后你在其中输入你的程序:

10 ?"COMMODORE 64"
20 GOTO 10

手册里解释说 10 和 20 是行号。它们为计算机排序了语句。它们也允许程序员在某些命令中引用程序的其它行,正像你在这里使用的 GOTO 命令一样,它将程序指回到行 10。“这是一个很好的编程习惯”,手册认为,“以 10 的增量来编号行,可以防止你以后需要插入一些语句”。

你输入 RUN,并凝视充满了 “COMMODORE 64” 的屏幕,它一遍又一遍的重复。

Commodore 64 显示反复打印 "Commodore 64" 的结果

你不确定这不会引爆你的计算机,过了一秒钟你才想起来应该单击 RUN/STOP 按键来打断循环。

手册接下来的一些部分向你介绍了变量,它告诉你变量像“在计算机中许多的盒子,它们每个可以容纳一个数字或一个文本字符串”。以一个 % 符号结尾的变量是一个整数,与此同时,以一个 $ 符号结尾的变量是一个字符串。其余的所有变量是一些称为“浮点”变量的东西。手册警告你要小心变量名称,因为计算机仅会识别变量名称的前两个字母,尽管它不限制你想创建的名称有多长。(这并没有特别让你困扰,但是要是在 30 年后来看,这可能会让人们感到太疯狂了)

你接着学习 IF... THEN...FOR... NEXT... 结构体。有了这些新的工具,你感觉有能力来解决接下来手册丢给你的重大挑战。“如果你是个有野心的人”,没错,“输入下面的程序,并查看会发生什么。”该程序比你目前为止看到的程序更长、更复杂,但是,你很想知道它做了什么:

10  REM BOUNCING BALL
20  PRINT "{CLR/HOME}"
25  FOR X = 1 TO 10 : PRINT "{CRSR/DOWN}" : NEXT
30  FOR BL = 1 TO 40
40  PRINT " ●{CRSR LEFT}";:REM (● is a Shift-Q)
50  FOR TM = 1 TO 5
60  NEXT TM
70  NEXT BL
75  REM MOVE BALL RIGHT TO LEFT
80  FOR BL = 40 TO 1 STEP -1
90  PRINT " {CRSR LEFT}{CRSR LEFT}●{CRSR LEFT}";
100 FOR TM = 1 TO 5
110 NEXT TM
120 NEXT BL
130 GOTO 20

上面的程序充分利用了 Commodore 64 最酷的功能之一。当把不可打印的命令字符作为字符串的一部分传递到 PRINT 命令时,它们会执行其操作,而不是被打印到屏幕上。这允许你重新摆放你程序中打印的字符串。(LCTT 译注:上述程序中如 {CRSR LEFT} 之类的控制字符执行类似 “在行中向左移动一个位置” 的操作,因此上述程序中利用这些字符操作了一个圆点字符四处移动,如下图。)

输入上面的程序你花费了很长时间。你犯一些错误,并不得不重新输入一些行。但是,你最终能够按下 RUN,并看到了一幅杰作:

Commodore 64 反弹球

你觉得这恐怕是你见过的最酷的事了。不过你几乎转头就忘记了它,因为马上你就学到了 BASIC 的内置函数,像 RND(它返回一个随机数字)和 CHR$(它返回与一个给定数字代码匹配的字符),这个手册向你展示一个程序,这个程序有名到什么程度呢?直到许多年后,它仍然被当成了一个短文选集的标题:

10 PRINT "{CLR/HOME}"
20 PRINT CHR$(205.5 + RND(1));
40 GOTO 20

当运行时,上面的程序会生成一个随机的迷宫:

Commodore 64 迷宫程序

这绝对是你曾经见过最酷的事。

PEEK 和 POKE

现在你已经看过了 Commodore 64 手册的前四章节,包含那篇 “高级的 BASIC” 的章节,所以你感到十分自豪。在这个星期六早上,你学习到了很多东西。但是这个下午(在赶快吃了点午饭后),你将继续学习一些使这个放在你的客厅中的奇妙机器变得不再神秘的东西。

手册中的下一个章节标题是“高级颜色和图像命令”。它从回顾你今天早上首先键入的彩色条开始,并向你展示了如何在一个程序中做同样的事。然后它教给了你如何更改屏幕的背景颜色。

为此,你需要使用 BASIC 的 PEEKPOKE 命令。这些命令分别允许你检查和写入一个存储器地址。Commodore 64 有一个主背景颜色和一个边框背景颜色。每个都通过一个特定的内存地址控制。你可以把你喜欢的任何颜色值写入到这些地址,以使用这些背景颜色和边框颜色。

手册中解释:

正像变量可以被认为机器中你放置信息的“盒子”一样,你也可以认为在计算机中代表特定内存位置的是一些特殊定义的“盒子”。

Commodore 64 会查看这些内存位置来了解屏幕的背景和边框应该是什么样的颜色,什么样的字符应该被显示在屏幕上,以及显示在哪里,等等其它任务。

你编写了一个程序来遍历所有可用的背景和边界的颜色的组合:

10 FOR BA = 0 TO 15
20 FOR BO = 0 TO 15
30 POKE 53280, BA
40 POKE 53281, BO
50 FOR X = 1 TO 500 : NEXT X
60 NEXT BO : NEXT BA

虽然 POKE 命令以及它的大操作数一开始时看起来很吓人,现在你看到那个数字的实际值其实不是很要紧。显然,你必须得到正确的数字,但是所有的数字代表的是一个“盒子”,Commodore 只是正好存储在地址 53280 处而已。这个盒子有一个特殊的用途:Commodore 使用它来确定屏幕背景应该是什么颜色。

Commodore 64 更改背景颜色

你认为这简直棒极了。只需要写入到内存中一个专用的盒子,你可以控制一台计算机的基础属性。你不确定 Commodore 64 的电路系统如何读取你写入在内存中的值并更改屏幕的颜色的,但是,你不知道这些也没事。至少你知道结果是怎么样的。

特殊容器

在那个周六,你没有读完整本手册,因为你现在有点精疲力尽了。但是你最终会全部读完它。在这个过程中,你学到更多的 Commodore 64 专用的盒子。有一些盒子你可以写入来控制在屏幕上显示什么——这也是一个盒子,事实上,是控制每一个位置出现的字符。在第六章节 “精灵图形” 中,你学到可以让你定义可以移动和甚至缩放图像的特殊盒子。在第七章节 “创造声音” 中,你学到能写入以便使你的 Commodore 64 歌唱 “Michael Row the Boat Ashore” 的盒子。Commodore 64,事实证明,它和你可能以后学习到的一个称为 API 的关系甚少。控制 Commodore 64 大多涉及写入到电路系统赋予特殊意义的内存地址。

多年来,你花费在这些特殊盒子的时光一直伴随着你。甚至几十年后,当你在一个拥有大量的图形或声音 API 的机器上编程时,你知道,隐藏于其背后的,这些 API 最终是写入到这些盒子之类的东西里面的。你有时会好奇那些只使用过 API 的年轻程序员,他们肯定是觉得 API 为他们做到的这一切。可能他们认为这些 API 调用了一些其它隐藏的 API。但是,那些隐藏的 API 调用了什么?你不由得同情这些年轻的程序员们,因为他们一定会非常迷惑。

如果你喜欢这篇文章,也喜欢它每两周发布的一篇新文章的话,那么请在 Twitter 上关注 @TwoBitHistory 或订阅 RSS 源来确保你知道新的文章发布出来。


via:https://twobithistory.org/2018/09/02/learning-basic.html

作者:Two-Bit History 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

如果你输入 dig 命令对 google.com 进行 DNS 查询,你会得到如下答复:

$ dig google.com

; <<>> DiG 9.10.6 <<>> google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27120
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;google.com.            IN  A

;; ANSWER SECTION:
google.com.     194 IN  A   216.58.192.206

;; Query time: 23 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Sep 21 16:14:48 CDT 2018
;; MSG SIZE rcvd: 55

这个输出一部分描述了你的问题(google.com 的 IP 地址是什么?),另一部分则详细描述了你收到的回答。在 答案区段 ANSWER SECTION 里,dig 为我们找到了一个包含五个字段的记录。从左数第四个字段 A 定义了这个记录的类型 —— 这是一个地址记录。在 A 的右边,第五个字段告知我们 google.com 的 IP 地址是 216.58.192.206。第二个字段,194 则代表这个记录的缓存时间是 194 秒。

那么,IN 字段告诉了我们什么呢?令人尴尬的是,在很长的一段时间里,我都认为这是一个介词。那时候我认为 DNS 记录大概是表达了“在 A 记录里,google.com 的 IP 地址是 216.58.192.206。”后来我才知道 IN 是 “internet” 的简写。IN 这一个部分告诉了我们这个记录分属的 类别 class

那么,除了 “internet” 之外,DNS 记录还会有什么别的类别吗?这究竟意味着什么?你怎么去搜寻一个不位于 internet 上的地址?看起来 IN 是唯一一个可能有意义的值。而且的确,如果你尝试去获得除了 IN 之外的,关于 google.com 的记录的话,DNS 服务器通常不能给出恰当的回应。以下就是我们尝试向 8.8.8.8(谷歌公共 DNS 服务器)询问在 HS 类别里 google.com 的 IP 地址。我们得到了状态为 SERVFAIL 的回复。

$ dig -c HS google.com

; <<>> DiG 9.10.6 <<>> -c HS google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 31517
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;google.com.            HS  A

;; Query time: 34 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Tue Sep 25 14:48:10 CDT 2018
;; MSG SIZE rcvd: 39

所以说,除了 IN 以外的类别没有得到广泛支持,但它们的确是存在的。除了 IN 之外,DNS 记录还有 HS(我们刚刚看到的)和 CH 这两个类别。HS 类是为一个叫做 Hesiod) 的系统预留的,它可以利用 DNS 来存储并让用户访问一些文本资料。它通常在本地环境中作为 LDAP 的替代品使用。而 CH 这个类别,则是为 Chaosnet 预留的。

如今,大家都在使用 TCP/IP 协议族。这两种协议(TCP 及 UDP)是绝大部分电脑远程连接采用的协议。不过我觉得,从互联网的垃圾堆里翻出了一个布满灰尘,绝迹已久,被人们遗忘的系统,也是一件令人愉悦的事情。那么,Chaosnet 是什么?为什么它像恐龙一样,走上了毁灭的道路呢?

在 MIT 的机房里

Chaosnet 是在 1970 年代,由 MIT 人工智能实验室的研究员们研发的。它是一个宏伟目标的一部分 —— 设计并制造一个能比其他通用电脑更高效率运行 Lisp 代码的机器。

Lisp 是 MIT 教授 John McCarthy 的造物,他亦是人工智能领域的先驱者。在 1960 年发布的一篇论文中,他首次描述了 Lisp 这个语言。在 1962 年,Lisp 的编译器和解释器诞生了。Lisp 引入了非常多的新特性,这些特性在现在看来是每一门编程语言不可或缺的一部分。它是第一门拥有垃圾回收器的语言,是第一个有 REPL(Read-eval-print-loop:交互式解析器)的语言,也是第一个支持动态类型的语言。在人工智能领域工作的程序员们都十分喜爱这门语言,比如说,大名鼎鼎的 SHRDLU 就是用它写的。这个程序允许人们使用自然语言,向机器下达挪动玩具方块这样的命令。

Lisp 的缺点是它太慢了。跟其它语言相比,Lisp 需要使用两倍的时间来执行相同的操作。因为 Lisp 在运行中仍会检查变量类型,而不仅是编译过程中。在 MIT 的 IBM 7090 上,它的垃圾回收器也需要长达一秒钟的时间来执行。 1 这个性能问题急需解决,因为 AI 研究者们试图搭建类似 SHRDLU 的应用。他们需要程序与使用者进行实时互动。因此,在 1970 年代的晚期,MIT 人工智能实验室的研究员们决定去建造一个能更高效运行 Lisp 的机器来解决这个问题。这些“Lisp 机器”们拥有更大的存储和更精简的指令集,更加适合 Lisp。类型检查由专门的电路完成,因此在 Lisp 运行速度的提升上达成了质的飞跃。跟那时流行的计算机系统不同,这些机器并不支持分时,整台电脑的资源都用来运行一个单独的 Lisp 程序。每一个用户都会得到他自己单独的 CPU。MIT 的 Lisp 机器小组 Lisp Machine Group 在一个备忘录里提到,这些功能是如何让 Lisp 运行变得更简单的:

Lisp 机器是个人电脑。这意味着处理器和主内存并不是分时复用的,每个人都能得到单独属于自己的处理器和内存。这个个人运算系统由许多处理器组成,每个处理器都有它们自己的内存和虚拟内存。当一个用户登录时,他就会被分配一个处理器,在他的登录期间这个处理器是独属于他的。当他登出,这个处理器就会重新可用,等待被分配给下一个用户。通过采取这种方法,当前用户就不用和其他用户竞争内存的使用,他经常使用的内存页也能保存在处理器核心里,因此页面换出的情况被显著降低了。这个 Lisp 机器解决了分时 Lisp 机器的一个基本问题。 2

这个 Lisp 机器跟我们认知的现代个人电脑有很大的不同。该小组原本希望今后用户不用直接面对 Lisp 机器,而是面对终端。那些终端会与位于别处的 Lisp 机器进行连接。虽然每个用户都有自己专属的处理器,但那些处理器在工作时会发出很大的噪音,因此它们最好是位于机房,而不是放在本应安静的办公室里。 3 这些处理器会通过一个“完全分布式控制”的高速本地网络共享访问一个文件系统和设备,例如打印机。 4 这个网络的名字就是 Chaosnet。

Chaosnet 既是硬件标准也是软件协议。它的硬件标准与以太网类似,事实上 Chaosnet 软件协议是运行在以太网之上的。这个软件协议在网络层和传输层之间交互,它并不像 TCP/IP,而总是控制着本地网络。Lisp 机器小组的一个成员 David Moon 写的另一个备忘录中提到,Chaosnet “目前并不打算为低速链接、高信噪链接、多路径、长距离链接做特别的优化。” 5 他们专注于打造一个在小型网络里表现极佳的协议。

因为 Chaosnet 连接在 Lisp 处理器和文件系统之间,所以速度十分重要。网络延迟会严重拖慢一些像打开文本文档这种简单操作的速度,为了提高速度,Chaosnet 结合了在 Network Control Program网络控制程序中使用的一些改进方法,随后的 Arpanet 项目中也使用了这些方法。据 Moon 所说,“为了突破诸如在 Arpanet 中发现的速率瓶颈,很有必要采纳新的设计。目前来看,瓶颈在于由多个链接分享控制链接,而且在下一个信息发送之前,我们需要知道本次信息已经送达。” 6 Chaosnet 协议族的批量 ACK 包跟当今 TCP 的差不多,它减少了 1/3 到一半的需要传输的包的数量。

因为绝大多数 Lisp 机器使用较短的单线进行连接,所以 Chaosnet 可以使用较为简单的路由算法。Moon 在 Chaosnet 路由方案中写道“预计要适配的网络架构十分简单,很少有多个路径,而且每个节点之间的距离很短。所以我认为没有必要进行复杂的方案设计。” 7 因为 Chaosnet 采用的算法十分简单,所以实现它也很容易。与之对比明显,其实现程序据说只有 Arpanet 网络控制程序的一半。 8

Chaosnet 的另一个特性是,它的地址只有 16 位,是 IPv4 地址的一半。所以这也意味着 Chaosnet 只能在局域网里工作。Chaosnet 也不会去使用端口号;当一个进程试图连接另一个机器上的另外一个进程时,需要首先初始化连接,获取一个特定的目标“ 联系名称 contact name ”。这个联系名称一般是某个特定服务的名字。比方说,一个主机试图使用 TELNET 作为联系名称,连接另一个主机。我认为它的工作方式在实践中有点类似于 TCP,因为有些非常著名的服务也会拥有联系名称,比如运行在 80 端口上的 HTTP 服务。

在 1986 年,RFC 973 通过了将 Chaosnet DNS 类别加入域名解析系统的决议。它替代了一个早先出现的类别 CSNETCSNET 是为了支持一个名叫 计算机科学网络 Computer Science Network 而被制造出来的协议。我并不知道为什么 Chaosnet 能被域名解析系统另眼相待。很多别的协议族也有资格加入 DNS,但是却被忽略了。比如 DNS 的主要架构师之一 Paul Mockapetris 提到说在他原本的构想里, 施乐 Xerox 的网络协议应该被包括在 DNS 里。 9 但是它并没有被加入。Chaosnet 被加入的原因大概是因为 Arpanet 项目和互联网的早期工作,有很多都在麻省剑桥的博尔特·贝拉尼克—纽曼公司,他们的雇员和 MIT 大多有紧密的联系。在这一小撮致力于发展计算机网络人中,Chaosnet 这个协议应该较为有名。

Chaosnet 随着 Lisp 机器的衰落渐渐变得不那么流行。尽管在一小段时间内 Lisp 机器有实际的商业产品 —— Symbolics 和 Lisp Machines Inc 在 80 年代售卖了这些机器。但它们很快被更便宜的微型计算机替代。这些计算机没有特殊制造的回路,但也可以快速运行 Lisp。Chaosnet 被制造出来的目的之一是解决一些 Apernet 协议的原始设计缺陷,但现在 TCP/IP 协议族同样能够解决这些问题了。

壳中幽灵

非常不幸的是,在互联网中留存的关于 Chaosnet 的资料不多。RFC 675 —— TCP/IP 的初稿于 1974 年发布,而 Chasnet 于 1975 年开始开发。 10 但 TCP/IP 最终征服了整个互联网世界,Chaosnet 则被宣布技术性死亡。尽管 Chaosnet 有可能影响了接下来 TCP/IP 的发展,可我并没有找到能够支持这个猜测的证据。

唯一一个可见的 Chaosnet 残留就是 DNS 的 CH 类。这个事实让我着迷。CH 类别是那被遗忘的幽魂 —— 在 TCP/IP 广泛部署中存在的一个替代协议 Chaosnet 的最后栖身之地。至少对于我来说,这件事情是十分让人激动。它告诉我关于 Chaosnet 的最后一丝痕迹,仍然藏在我们日常使用的网络基础架构之中。DNS 的 CH 类别是有趣的数码考古学遗迹。但它同时也是活生生的标识,提醒着我们互联网并非天生完整成型的,TCP/IP 不是唯一一个能够让计算机们交流的协议。“万维网”也远远不是我们这全球交流系统所能有的,最酷的名字。


  1. LISP 1.5 Programmer’s Manual, The Computation Center and Research Laboratory of Electronics, 90, accessed September 30, 2018, http://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf
  2. Lisp Machine Progress Report (Artificial Intelligence Memo 444), MIT Artificial Intelligence Laboratory, August, 1977, 3, accessed September 30, 2018, https://dspace.mit.edu/bitstream/handle/1721.1/5751/AIM-444.pdf.
  3. Lisp Machine Progress Report (Artificial Intelligence Memo 444), 4.
  4. 同上
  5. Chaosnet (Artificial Intelligence Memo 628), MIT Artificial Intelligence Laboratory, June, 1981, 1, accessed September 30, 2018, https://dspace.mit.edu/bitstream/handle/1721.1/6353/AIM-628.pdf.
  6. 同上
  7. Chaosnet (Artificial Intelligence Memo 628), 16.
  8. Chaosnet (Artificial Intelligence Memo 628), 9.
  9. Paul Mockapetris and Kevin Dunlap, “The Design of the Domain Name System,” Computer Communication Review 18, no. 4 (August 1988): 3, accessed September 30, 2018, http://www.cs.cornell.edu/people/egs/615/mockapetris.pdf.
  10. Chaosnet (Artificial Intelligence Memo 628), 1.

via: https://twobithistory.org/2018/09/30/chaosnet.html

作者:Two-Bit History 选题:lujun9972 译者:acyanbird 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

尽管我很难说清楚为什么,但 Ruby 一直是我最喜爱的一门编程语言。如果用音乐来类比的话,Python 给我的感觉像是 朋克摇滚 punk rock ,简单、直接,但略显单调,而 Ruby 则像是爵士乐,从根本上赋予了程序员表达自我的自由,虽然这可能会让代码变复杂,编写出来的程序对其他人来说不直观。

Ruby 社区一直将 灵活表达 freedom of expression 视为其核心价值。可我不认同这对于 Ruby 的开发和普及是最重要的。创建一门编程语言也许是为了更高的性能,也许是为了在抽象上节省更多的时间,可 Ruby 就有趣在它并不关心这些,从它诞生之初,它的目标就是让程序员更快乐。

松本·行弘

松本·行弘 Yukihiro Matsumoto ,亦称为 “Matz”,于 1990 年毕业于筑波大学。筑波是东京东北方向上的一个小城市,是科学研究与技术开发的中心之一。筑波大学以其 STEM 计划广为流传。松本·行弘在筑波大学的信息科学专业学习过,且专攻编程语言。他也在 Ikuo Nakata 的编程语言实验室工作过。(LCTT 译注:STEM 是 科学 Science 技术 Technology 工程 Engineering 数学 Mathematics 四门学科英文首字母的缩写。)

松本从 1993 年开始制作 Ruby,那时他才刚毕业几年。他制作 Ruby 的起因是觉得那时的脚本语言缺乏一些特性。他在使用 Perl 的时候觉得这门语言过于“玩具”,此外 Python 也有点弱,用他自己的话说:

我那时就知道 Python 了,但我不喜欢它,因为我认为它不是一门真正的面向对象的语言。面向对象就像是 Python 的一个附件。作为一个编程语言狂热者,我在 15 年里一直是面向对象的忠实粉丝。我真的想要一门生来就面向对象而且易用的脚本语言。我为此特地寻找过,可事实并不如愿。 1

所以一种解释松本创造 Ruby 的动机就是他想要创造一门更好,且面向对象的 Perl。

但在其他场合,松本说他创造 Ruby 主要是为了让他自己和别人更快乐。2008 年,松本在谷歌技术讲座结束时放映了这张幻灯片:

他对听众说到,

我希望 Ruby 能帮助世界上的每一个程序员更有效率地工作,享受编程并感到快乐。这也是制作 Ruby 语言的主要意图。 2

松本开玩笑的说他制作 Ruby 的原因很自私,因为他觉得其它的语言乏味,所以需要创造一点让自己开心的东西。

这张幻灯片展现了松本谦虚的一面。其实,松本是一位摩门教践行者,因此我很好奇他传奇般的友善有多少归功于他的宗教信仰。无论如何,他的友善在 Ruby 社区广为流传,甚至有一条称为 MINASWAN 的原则,即“ 松本人很好,我们也一样 Matz Is Nice And So We Are Nice ”。我想那张幻灯片一定震惊了来自 Google 的观众。我想谷歌技术讲座上的每张幻灯片都充满着代码和运行效率的指标,来说明一个方案比另一个更快更有效,可仅仅放映崇高的目标的幻灯片却寥寥无几。

Ruby 主要受到 Perl 的影响。Perl 则是由 Larry Wall 于 20 世纪 80 年代晚期创造的语言,主要用于处理和转换基于文本的数据。Perl 因其文本处理和正则表达式而闻名于世。对于 Ruby 程序员,Perl 程序中的很多语法元素都不陌生,例如符号 $、符号 @elsif 等等。虽然我觉得,这些不是 Ruby 应该具有的特征。除了这些符号外,Ruby 还借鉴了 Perl 中的正则表达式的处理和标准库。

但影响了 Ruby 的不仅仅只有 Perl 。在 Ruby 之前,松本制作过一个仅用 Emacs Lisp 编写的邮件客户端。这一经历让他对 Emacs 和 Lisp 语言运行的内部原理有了更多的认识。松本说 Ruby 底层的对象模型也受其启发。在那之上,松本添加了一个 Smalltalk 风格的信息传递系统,这一系统随后成为了 Ruby 中任何依赖 #method_missing 的操作的基石。松本也表示过 Ada 和 Eiffel 也影响了 Ruby 的设计。

当时间来到了给这门新语言命名的时候,松本和他的同事 Keiju Ishitsuka 挑选了很多个名字。他们希望名字能够体现新语言和 Perl、shell 脚本间的联系。在这一段非常值得一读的即时消息记录中,Ishitsuka 和 松本也许花了太多的时间来思考 shell 贝壳 clam 蛤蛎 oyster 牡蛎 pearl 珍珠 之间的关系了,以至于差点把 Ruby 命名为“ Coral 珊瑚虫 ”或“ Bisque 贝类浓汤 ”。幸好,他们决定使用 Ruby,因为它就像 pearl 一样,是一种珍贵的宝石。此外, Ruby 红宝石 还是 7 月的生辰石,而 Pearl 珍珠 则是 6 月的生辰石,采用了类似 C++ 和 C# 的隐喻,暗示着她们是改进自前辈的编程语言。(LCTT 译注:Perl 和 Pearl 发音相同,所以也常以“珍珠”来借喻 Perl;shell 是操作系统提供的用户界面,这里指的是命令行界面;更多有关生辰石的信息。)

Ruby 西渐

Ruby 在日本的普及很快。1995 年 Ruby 刚刚发布后不久后,松本就被一家名为 Netlab 的日本软件咨询财团(全名 Network Applied Communication Laboratory)雇用,并全职为 Ruby 工作。到 2000 年时,在 Ruby 发布仅仅 5 年后,Ruby 在日本的流行度就超过了 Python。可这时的 Ruby 才刚刚进入英语国家。虽然从 Ruby 的诞生之初就存在讨论它的日语邮件列表,但是英语的邮件列表直到 1998 年才建立起来。起初,在英语的邮件列表中交流的大多是日本的 Ruby 狂热者,可随着 Ruby 在西方的逐渐普及而得以改变。

在 2000 年,Dave Thomas 出版了第一本涵盖 Ruby 的英文书籍《Programming Ruby》。因为它的封面上画着一把锄头,所以这本书也被称为锄头书。这是第一次向身处西方的程序员们介绍了 Ruby。就像在日本那样,Ruby 的普及很快,到 2002 年时,英语的 Ruby 邮件列表的通信量就超过了日语邮件列表。

时间来到了 2005 年,Ruby 更流行了,但它仍然不是主流的编程语言。然而,Ruby on Rails 的发布让一切都不一样了。Ruby on Rails 是 Ruby 的“杀手级应用”,没有别的什么项目能比它更推动 Ruby 的普及了。在 Ruby on Rails 发布后,人们对 Ruby 的兴趣爆发式的增长,看看 TIOBE 监测的语言排行:

有时人们开玩笑的说,Ruby 程序全是基于 Ruby-on-Rails 的网站。虽然这听起来就像是 Ruby on Rails 占领了整个 Ruby 社区,但在一定程度上,这是事实。因为编写 Rails 应用时使用的语言正是 Ruby。Rails 欠 Ruby 的和 Ruby 欠 Rails 的一样多。

Ruby 的设计哲学也深深地影响了 Rails 的设计与开发。Rails 之父 David Heinemeier Hansson 常常提起他第一次与 Ruby 的接触的情形,那简直就是一次传教。他说,那种经历简直太有感召力了,让他感受到要为松本的杰作(指 Ruby)“传教”的使命。 3 对于 Hansson 来说,Ruby 的灵活性简直就是对 Python 或 Java 语言中自上而下的设计哲学的反抗。他很欣赏 Ruby 这门能够信任自己的语言,Ruby 赋予了他自由选择 程序表达方式 express his programs 的权力。

就像松本那样,Hansson 声称他创造 Rails 时因为对现状的不满并想让自己能更开心。他也认同让程序员更快乐高于一切的观点,所以检验 Rails 是否需要添加一项新特性的标准是“ 更灿烂的笑容标准 The Principle of The Bigger Smile ”。什么功能能让 Hansson 更开心就给 Rails 添加什么。因此,Rails 中包括了很多非正统的功能,例如 “Inflector” 类和 Time 扩展(“Inflector”类试图将单个类的名字映射到多个数据库表的名字;Time 扩展允许程序员使用 2.days.ago 这样的表达式)。可能会有人觉得这些功能太奇怪了,但 Rails 的成功表明它的确能让很多人的生活得更快乐。

因此,虽然 Rails 的火热带动了 Ruby 的普及看起来是一个偶然,但事实上 Rails 体现了 Ruby 的很多核心准则。此外,很难看到使用其他语言开发的 Rails,正是因为 Rails 的实现依赖于 Ruby 中 类似于宏的类方法调用 macro-like class method calls 来实现模型关联这样的功能。一些人认为这么多的 Ruby 开发需要基于 Ruby on Rails 是 Ruby 生态不健康的表现,但 Ruby 和 Ruby on Rails 结合的如此紧密并不是没有道理的。

Ruby 之未来

人们似乎对 Ruby(及 Ruby on Rails)是否正在消亡有着异常的兴趣。早在 2011 年,Stack Overflow 和 Quora 上就充斥着程序员在咨询“如果几年后不再使用 Ruby 那么现在是否有必要学它”的话题。这些担忧对 Ruby 并非没有道理,根据 TIOBE 指数和 Stack Overflow 趋势,Ruby 和 Ruby on Rails 的人气一直在萎缩,虽然它也曾是热门新事物,但在更新更热的框架面前它已经黯然失色。

一种解释这种趋势的理论是程序员们正在舍弃动态类型的语言转而选择静态类型的。TIOBE 指数的趋势中可以看出对软件质量的需求在上升,这意味着出现在运行时的异常变得难以接受。他们引用 TypeScript 来说明这一趋势,TypeScript 是 JavaScript 的全新版本,而创造它的目的正是为了保证客户端运行的代码能受益于编译所提供的安全保障。

我认为另一个更可能的原因是比起 Ruby on Rails 推出的时候,现在存在着更多有竞争力的框架。2005 年它刚刚发布的时候,还没有那么多用于创建 Web 程序的框架,其主要的替代者还是 Java。可在今天,你可以使用为 Go、Javascript 或者 Python 开发的各种优秀的框架,而这还仅仅是主流的选择。Web 的世界似乎正走向更加分布式的结构,与其使用一块代码来完成从数据库读取到页面渲染所有事务,不如将事务拆分到多个组件,其中每个组件专注于一项事务并将其做到最好。在这种趋势下,Rails 相较于那些专攻于 JavaScript 前端通信的 JSON API 就显得过于宽泛和臃肿。

总而言之,我们有理由对 Ruby 的未来持乐观态度。因为不管是 Ruby 还是 Rails 的开发都还很活跃。松本和其他的贡献者们都在努力开发 Ruby 的第三个主要版本。新的版本将比现在的版本快上 3 倍,以减轻制约着 Ruby 发展的性能问题。虽然从 2005 年起,越来越多的 Web 框架被开发出来,但这并不意味着 Ruby on Rails 就失去了其生存空间。Rails 是一个富有大量功能的成熟的工具,对于一些特定类型的应用开发一直是非常好的选择。

但就算 Ruby 和 Rails 走上了消亡的道路,Ruby 让程序员更快乐的信条一定会存活下来。Ruby 已经深远的影响了许多新的编程语言的设计,这些语言的设计中能够看到来自 Ruby 的很多理念。而其他的新生语言则试着变成 Ruby 更现代的实现,例如 Elixir 是一个强调函数式编程范例的语言,仍在开发中的 Crystal 目标是成为使用静态类型的 Ruby 。世界上许多程序员都喜欢上了 Ruby 及其语法,因此它的影响必将会在未来持续很长一段时间。

喜欢这篇文章吗?这里每两周都会发表一篇这样的文章。请在推特上关注我们 @TwoBitHistory 或者订阅我们的 RSS,这样新文章发布的第一时间你就能得到通知。


  1. http://ruby-doc.org/docs/ruby-doc-bundle/FAQ/FAQ.html
  2. https://www.youtube.com/watch?v=oEkJvvGEtB4?t=30m55s
  3. http://rubyonrails.org/doctrine/

via: https://twobithistory.org/2017/11/19/the-ruby-story.html

作者:Two-Bit History 选题:lujun9972 译者:wwhio 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出