Two-bit History 发布的文章

JSON 已经占领了全世界。当今,任何两个应用程序彼此通过互联网通信时,可以打赌它们在使用 JSON。它已被所有大型企业所采用:十大最受欢迎的 web API 接口列表中(主要由 Google、Facebook 和 Twitter 提供),仅仅只有一个 API 接口是以 XML 的格式开放数据的。 1 这个列表中的 Twitter API 为此做了一个鲜活的注脚:其对 XML 格式的支持到 2013 年结束,其时发布的新版本的 API 取消 XML 格式,转而仅使用 JSON。JSON 也在程序编码级别和文件存储上被广泛采用:在 Stack Overflow(LCTT 译注:一个面向程序员的问答网站)上,现在更多的是关于 JSON 的问题,而不是其他的数据交换格式。 2

XML 仍然在很多地方存在。网络上它被用于 SVG 和 RSS / Atom 信息流。Android 开发者想要获得用户权限许可时,需要在其 APP 的 manifest 文件中声明 —— 此文件是 XML 格式的。XML 的替代品也不仅仅只有 JSON,现在有很多人在使用 YAML 或 Google 的 Protocol Buffers 等技术,但这些技术的受欢迎程度远不如 JSON。目前来看,JSON 是应用程序在网络之间通信的首选协议格式。

考虑到自 2005 年来 Web 编程世界就垂涎于 “异步 JavaScript 和 XML” 而非 “异步 JavaScript 和 JSON” 的技术潜力,你可以发现 JSON 的主导地位是如此的让人惊讶。当然了,这可能与这两种通信格式的受欢迎程度无关,而仅反映出缩写 “AJAX” 似乎比 “AJAJ” 更具吸引力。但是,即使在 2005 年有些人(实际上没有太多人)已经用 JSON 来取代 XML 了,我们不禁要问 XML 的噩运来的如此之快,以至于短短十来年,“异步 JavaScript 和 XML” 这个名称就成为一个很讽刺的误称。那这十来年发生了什么?JSON 怎么会在那么多应用程序中取代了 XML?现在被全世界工程师和系统所使用、依赖的这种数据格式是谁提出的?

JSON 之诞生

2001 年 4 月,首个 JSON 格式的消息被发送出来。此消息是从旧金山湾区某车库的一台计算机发出的,这是计算机历史上重要的的时刻。Douglas Crockford 和 Chip Morningstar 是一家名为 State Software 的技术咨询公司的联合创始人,他们当时聚集在 Morningstar 的车库里测试某个想法,发出了此消息。

在 “AJAX” 这个术语被创造之前,Crockford 和 Morningstar 就已经在尝试构建好用的 AJAX 应用程序了,可是浏览器对其兼容性不好。他们想要在初始页面加载后就将数据传递给应用程序,但其目标要针对所有的浏览器,这就实现不了。

这在今天看来不太可信,但是要记得 2001 年的时候 Internet Explorer(IE)代表了网页浏览器的最前沿技术产品。早在 1999 年的时候,Internet Explorer 5 就支持了原始形式的 XMLHttpRequest,开发者可以使用名为 ActiveX 的框架来访问此对象。Crockford 和 Morningstar 能够使用此技术(在 IE 中)来获取数据,但是在 Netscape 4 中(这是他们想要支持的另一种浏览器)就无法使用这种解决方案。为此 Crockford 和 Morningstar 只得使用一套不同的系统以兼容不同的浏览器。

第一条 JSON 消息如下所示:

<html><head><script>
    document.domain = 'fudco';
    parent.session.receive(
        { to: "session", do: "test",
          text: "Hello world" }
    )
</script></head></html>

消息中只有一小部分类似于今天我们所知的 JSON,本身其实是一个包含有一些 JavaScript 的 HTML 文档。类似于 JSON 的部分只是传递给名为 receive() 的函数的 JavaScript 对象字面量。

Crockford 和 Morningstar 决定滥用 HTML 的帧(<frame>)以发送数据。他们可以让一个帧指向一个返回的上述 HTML 文档的 URL。当接收到 HTML 时,JavaScript 代码段就会运行,就可以把数据对象字面量如实地传递回应用程序。只要小心的回避浏览器保护策略(即子窗口不允许访问父窗口),这种技术就可以正常运行无误。可以看到 Crockford 和 Mornginstar 通过明确地设置文档域这种方法来达到其目的。(这种基于帧的技术,有时称为隐藏帧技术,通常在 90 年代后期,即在广泛使用 XMLHttpRequest 技术之前使用。 3

第一个 JSON 消息的惊人之处在于,它显然不是一种新的数据格式的首次使用。它就是 JavaScript!实际上,以此方式使用 JavaScript 的想法如此简单,Crockford 自己也说过他不是第一个这样做的人。他说 Netscape 公司的某人早在 1996 年就使用 JavaScript 数组字面量来交换信息。 4 因为消息就是 JavaScript,其不需要任何特殊解析工作,JavaScript 解释器就可搞定一切。

最初的 JSON 信息实际上与 JavaScript 解释器发生了冲突。JavaScript 保留了大量的关键字(ECMAScript 6 版本就有 64 个保留字),Crockford 和 Morningstar 无意中在其 JSON 中使用了一个保留字。他们使用了 do 作为了键名,但 do 是解释器中的保留字。因为 JavaScript 使用的保留字太多了,Crockford 做了决定:既然不可避免的要使用到这些保留字,那就要求所有的 JSON 键名都加上引号。被引起来的键名会被 JavaScript 解释器识别成字符串,其意味着那些保留字也可以放心安全的使用。这就为什么今天 JSON 键名都要用引号引起来的原因。

Crockford 和 Morningstar 意识到这技术可以应用于各类应用系统。想给其命名为 “JSML”,即 JavaScript 标记语言 JavaScript Markup Language 的意思,但发现这个缩写已经被一个名为 Java Speech 标记语言的东西所使用了。因此他们决定采用 “JavaScript Object Notation”,缩写为 JSON。他们开始向客户推销,但很快发现客户不愿意冒险使用缺乏官方规范的未知技术。所以 Crockford 决定写一个规范。

2002 年,Crockford 买下了 JSON.org 域名,放上了 JSON 语法及一个解释器的实例例子。该网站仍然在运行,现在已经包含有 2013 年正式批准的 JSON ECMA 标准的显著链接。在该网站建立后,Crockford 并没有过多的推广,但很快发现很多人都在提交各种不同编程语言的 JSON 解析器实现。JSON 的血统显然与 JavaScript 相关联,但很明显 JSON 非常适合于不同语言之间的数据交换。

AJAX 导致的误会

2005 年,JSON 有了一次大爆发。那一年,一位名叫 Jesse James Garrett 的网页设计师和开发者在博客文章中创造了 “AJAX” 一词。他很谨慎地强调:AJAX 并不是新技术,而是 “好几种蓬勃发展的技术以某种强大的新方式汇集在一起。 5 ” AJAX 是 Garrett 给这种正受到青睐的 Web 应用程序的新开发方法的命名。他的博客文章接着描述了开发人员如何利用 JavaScript 和 XMLHttpRequest 构建新型应用程序,这些应用程序比传统的网页更具响应性和状态性。他还以 Gmail 和 Flickr 网站已经使用 AJAX 技术作为了例子。

当然了,“AJAX” 中的 “X” 代表 XML。但在随后的问答帖子中,Garrett 指出,JSON 可以完全替代 XML。他写道:“虽然 XML 是 AJAX 客户端进行数据输入、输出的最完善的技术,但要实现同样的效果,也可以使用像 JavaScript Object Notation(JSON)或任何类似的结构数据方法等技术。 ” 6

开发者确实发现在构建 AJAX 应用程序时可以很容易的使用 JSON,许多人更喜欢它而不是 XML。具有讽刺意味的是,对 AJAX 的兴趣逐渐的导致了 JSON 的普及。大约在这个时候,JSON 引起了博客圈的注意。

2006 年,Dave Winer,一位高产的博主,他也是许多基于 XML 的技术(如 RSS 和 XML-RPC)背后的开发工程师,他抱怨到 JSON 毫无疑问的正在重新发明 XML。尽管人们认为数据交换格式之间的竞争不会导致某一技术的消亡。其写到:

毫无疑问,我可以编写一个例程来解析 JSON,但来看看他们要重新发明一个东西有多大的意义,出于某种原因 XML 本身对他们来说还不够好(我很想听听原因)。谁想干这荒谬之事?查找一棵树然后把节点串起来。可以立马试试。 7

我很理解 Winer 的挫败感。事实上并没有太多人喜欢 XML。甚至 Winer 也说过他不喜欢 XML。 8 但 XML 已被设计成一个可供任何人使用,并且可以用于几乎能想象到的所有事情。归根到底,XML 实际上是一门元语言,允许你为特定应用程序自定义该领域特定的语言。如 Web 信息流技术 RSS 和 SOAP(简单对象访问协议)就是例子。Winer 认为由于通用交换格式所带来的好处,努力达成共识非常重要。XML 的灵活性应该能满足任何人的需求,然而 JSON 格式呢,其并没有比 XML 提供更多东西,除了它抛弃了使 XML 更灵活的那些繁琐的东西。

Crockford 阅读了 Winer 的这篇文章并留下了评论。为了回应 JSON 重新发明 XML 的指责,Crockford 写到:“重造轮子的好处是可以得到一个更好的轮子。” 9

JSON 与 XML 对比

到 2014 年,JSON 已经由 ECMA 标准和 RFC 官方正式认可。它有了自己的 MIME 类型。JSON 已经进入了大联盟时代。

为什么 JSON 比 XML 更受欢迎?

JSON.org 网站上,Crockford 总结了一些 JSON 的优势。他写到,JSON 的语法极少,其结构可预测,因此 JSON 更容易被人类和机器理解。 10 其他博主一直关注 XML 的冗长啰嗦及“尖括号负担”。 11 XML 中每个开始标记都必须与结束标记匹配,这意味着 XML 文档包含大量的冗余信息。在未压缩时,XML 文档的体积比同等信息量 JSON 文档的体积大很多,但是,更重要的,这也使 XML 文档更难以阅读。

Crockford 还声称 JSON 的另一个巨大优势是其被设计为数据交换格式。 12 从一开始,它的目的就是在应用程序间传递结构化信息的。而 XML 呢,虽然也可以使用来传递数据,但其最初被设计为文档标记语言。它演变自 SGML(通用标准标记语言),而它又是从被称为 Scribe 的标记语言演变而来,其旨在发展成类似于 LaTeX 一样的文字处理系统。XML 中,一个标签可以包含有所谓的“混合内容”,即带有围绕单词、短语的内嵌标签的文本。这会让人浮现出一副用红蓝笔记录的手稿画面,这是标记语言核心思想的形象比喻。另一方面,JSON 不支持对混合内容模型的清晰构建,但这也意味着它的结构足够简单。一份文档最好的建模就是一棵树,但 JSON 抛弃了这种文档的思想,Crockford 将 JSON 抽象限制为字典和数组,这是所有程序员构建程序时都会使用的最基本的、也最熟悉的元素。

最后,我认为人们不喜欢 XML 是因为它让人困惑。它让人迷惑的地方就是有很多不同的风格。乍一看,XML 本身及其子语言(如 RSS、ATOM、SOAP 或 SVG)之间的界限并不明显。典型的 XML 文档的第一行标识了该 XML 的版本,然后该 XML 文档应该符合特定的子语言。这就有变化需要考虑了,特别是跟 JSON 做比较,JSON 是如此简单,以至于永远不需要编写新版本的 JSON 规范。XML 的设计者试图将 XML 做为唯一的数据交换格式以支配所有格式,就会掉入那个经典的程序员陷阱:过度工程化。XML 非常笼统及概念化,所以很难于简单的使用。

在 2000 年的时候,发起了一场使 HTML 符合 XML 标准的活动,发布了一份符合 XML 标准的 HTML 开发规范,这就此后很出名的 XHTML。虽然一些浏览器厂商立即开始支持这个新标准,但也很明显,大部分基于 HTML 技术的开发者不愿意改变他们的习惯。新标准要求对 XHTML 文档进行严格的验证,而不是基于 HTML 的基准。但大多的网站都是依赖于 HTML 的宽容规则的。到 2009 年的时候,试图编写第二版本的 XHTML 标准的努力已经流产,因为未来已清晰可见,HTML 将会发展为 HTML5(一种不强制要求接受 XML 规则的标准)。

如果 XHTML 的努力取得了成功,那么 XML 可能会成为其设计者所希望的通用数据格式。想象一下,一个 HTML 文档和 API 响应具有完全相同结构的世界。在这样的世界中,JSON 可能不会像现在一样普遍存在。但我把 HTML 的失败看做是 XML 阵营的一种道义上的失败。如果 XML 不是 HTML 的最佳工具,那么对于其他应用程序,也许会有更好的工具出现。在这个世界,我们的世界,很容易看到像 JSON 格式这样的足够简单、量体裁衣的才能获得更大的成功。

如果你喜欢这博文,每两周会更新一次! 请在 Twitter 上关注 @TwoBitHistory 或订阅 RSS feed,以确保得到更新的通知。


  1. http://www.cs.tufts.edu/comp/150IDS/final_papers/tstras01.1/FinalReport/FinalReport.html#software
  2. https://insights.stackoverflow.com/trends?tags=json%2Cxml%2Cprotocol-buffers%2Cyaml%2Ccsv
  3. Zakas, Nicholas C., et al. “What Is Ajax?” Professional Ajax, 2nd ed., Wiley, 2007.
  4. https://youtu.be/-C-JoyNuQJs?t=32s
  5. http://adaptivepath.org/ideas/ajax-new-approach-web-applications/
  6. 同上
  7. http://scripting.com/2006/12/20.html
  8. http://blogoscoped.com/archive/2009-03-05-n15.html
  9. https://scripting.wordpress.com/2006/12/20/scripting-news-for-12202006/#comment-26383
  10. http://www.json.org/xml.html
  11. https://blog.codinghorror.com/xml-the-angle-bracket-tax
  12. https://youtu.be/-C-JoyNuQJs?t=33m50sgg

via: https://twobithistory.org/2017/09/21/the-rise-and-rise-of-json.html

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

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

我知道这很学院派,可一行超过 80 个字符的代码还是让我抓狂。我也在网上见过不少人认为即使在现代的视网膜屏幕下也应当采用行长度为 80 个字符的标准,可他们都不理解我对破坏这一标准的怒火,就算多 1 个字符也不行。

在这一标准的黄金时期,一行代码的长度几乎不会超过 80 个字符的限制。在那时,这一限制是物理的,没有第 81 列用于存放第 81 个字符。每一个试图把函数名起的又长又臭的程序员都会在短暂的愉悦后迎来更多的麻烦,而这仅仅是因为没有足够的空间放下整个函数的声明。

这一黄金时期也是 打孔卡 punch card 编程时期。在 20 世纪 60 年代,IBM 打孔卡设立了标准,这个标准就是打孔卡的宽度为 80 列。这个 80 列标准在后来的电传打字机和哑终端时期得以延续,并逐渐成为操作系统中隐藏的细节。时至今日,即使我们用上了更大、更好的屏幕,偏向于使用更长的标识符而不是类似 iswcntrl() 这样令人难以猜测的函数名,可当你打开新的终端模拟器窗口时,默认的宽度依然是 80 个字符。

从 Quora 上的很多问题中可以发现,很多人并不能想象如何使用打孔卡给计算机编程。我承认,在很长的一段时间里我也不能理解打孔卡编程是如何工作的,因为这让我想到就像劳工一样不停的给这些打孔卡打孔。当然,这是一个误解,程序员不需要亲自给打孔卡打孔,就像是火车调度员不用亲自扳道岔。程序员们有 打孔机 card punch machines (也被称为 键控打孔机 key punches ),这让他们可以使用打字机式的键盘给打孔卡打孔。这样的设备在 19 世纪 90 年代时就已经不是什么新技术了。

那时,最为广泛使用的打孔机之一便是 IBM 029 型打孔机。就算在今天,它也许是最棒的打孔机。

IBM 029 型打孔机在 1964 年作为 IBM 的 System/360 大型电脑的配件发售的。System/360 是计算系统与外设所组成的一个系列,在 20 世纪 60 年代晚期,它几乎垄断了整个大型计算机市场。就像其它 System/360 外设一样,029 型打孔机也是个大块头。那时,计算机和家具的界限还很模糊,但 029 型打孔机可不是那种会占领你的整张桌子的机器。它改进自 026 型打孔机,增加了新的字符支持,如括号,总体上也更加安静。与前辈 026 型所展出 20 世纪 40 年代的圆形按钮与工业化的样貌相比,029 型的按键方正扁平、功能按键还有酷炫的蓝色高亮提示。它的另一个重要买点是它能够在 数字区 numeric field 左侧自动的填充 0 ,这证明了 JavaScript 程序员不是第一批懒得自己做 左填充 left-padding 的程序员。(LCTT 译注:这项功能需要额外的 4 张 标准模块系统卡 SMS card 才能使用。例如设置数字区域长度为 6 列时,操作员只需要输入 73 ,打孔机会自动填充起始位置上的 4 个 0 ,故最终输出 000073。更多信息

等等!你说的是 IBM 在 1964 年发布了全新的打孔机?你知道那张在贝尔实验室拍摄的 Unix 之父正在使用电传打字机的照片吗?那是哪一年的来着?1970?打孔机不是应该在 20 世纪 60 年代中期到晚期时就过时了吗?是的,你也许会奇怪,为什么直到 1984 年,IBM 的产品目录中还会出现 029 型打孔机的身影 1 。事实上,直到 20 世纪 70 年代,大多数程序员仍然在使用打孔卡编程。其实二战期间就已经有人在用电传打字机了,可那时并没能普及。客观的讲,电传打字机几乎和打孔卡一样古老。也许和你想象的恰恰相反,并不是电传打字机本身限制了它的普及,而是计算时间。人们拒绝使用电传打字机的原因是,它是可交互的,它和计算机使用 “在线”的传输方式 online mode of communication 。在以 Unix 为代表的分时操作系统被发明前,你和电脑的交互会被任何人的使用而打断,而这一点延迟通常意味着几千美元的损失。所以程序员们普遍选择离线地使用打孔机编程,再将打孔卡放入大型计算机中,作为 批任务 batch job 执行。在那时,还没有即廉价又可靠的存储设备,可打孔卡的廉价优势已经足够让它成为那时最流行的数据存储方式了。那时的程序是书架上一摞打孔卡而不是硬盘里的一堆文件。

那么实际使用 IBM 029 型打孔机是个什么样子呢?这很难向没有实际看过打孔卡的人解释。一张打孔卡通常有 12 行 80 列。打孔卡下面是从 1 到 9 的 数字行 digit rows ,打孔卡上的每一列都有这些行所对应的数字。最上面的三行是 空间行 zone rows ,通常由两行空白行和一行 0 行组成。第 12 行是打孔卡最顶部的行,接下来是 11 行,随后是从数字 0 到 9 所在的行。这个有点让人感到困惑的顺序的原因是打孔卡的上边缘被称为 12 边 12 edge 、下边缘被称为 9 边 9 edge 。那时,为了让打孔卡便于整理,常常会剪去打孔卡的一个角。

(LCTT 译注:可参考EBCDIC 编码

在打孔卡发明之初,孔洞的形状是圆形的,但是 IBM 最终意识到如果使用窄长方形作为孔洞,一张卡就可以放下更多的列了。每一列中孔洞的不同组合就可以表达不同的字符。像 029 型这样的拥有人性化设计的打孔机除了完成本质的打孔任务外,还会在打孔卡最上方打印出每一列所对应的字符。输入是数字就在对应的数字行上打孔。输入的是字母或符号就用一个在空间列的孔和一或俩个在数字列的孔的组合表示,例如字母 A 就用一个在第 12 空间行的空和一个数字 1 所在行的孔表示。这是一种顺序编码,在第一台打孔机被发明后,也叫 Hollerith 编码。这种编码只能表示相对较小的一套字符集,小写字母就没有包含在这套字符集中。如今一些聪明的工程师可能想知道为什么打卡不干脆使用二进制编码 —— 毕竟,有 12 行,你可以编码超过 4000 个字符。 使用 Hollerith 编码是因为它确保在单个列中出现不超过三个孔。这保留了卡的结构强度。二进制编码会带来太多的孔,会因为孔洞过于密集而断裂。

打孔卡也有不同。在 20 世纪 60 年代,80 列虽然是标准,但表达的方式不一定相同。基础打孔卡是无标注的,但用于 COBOL 编程的打孔卡会把最后的 8 列保留,供标识数保存使用。这一标识数可以在打孔卡被打乱 (例如一叠打孔卡掉在地上了) 后用于自动排序。此外,第 7 列被用于表示本张打孔卡上的是否与上一张打孔卡一起构成一条语句。也就是说当你真的对 80 字符的限制感到绝望的时候,还可以用两张卡甚至更多的卡拼接成一条长语句。用于 FORTRAN 编程的打孔卡和 COBOL 打孔卡类似,但是定义的列不同。大学里使用的打孔卡通常会由其计算机中心加上水印,其它的设计则会在如 1976 年美国独立 200 周年 的特殊场合才会加入。

最终,这些打孔卡都要被计算机读取和计算。IBM 出售的 System/360 大型计算机的外设 IBM 2540 可以以每分钟 1000 张打孔卡的速度读取这些卡片 2 。IBM 2540 使用电刷扫过每张打孔卡,电刷通过孔洞就可以接触到卡片后面的金属板完成一次读取。一旦读取完毕,System/360 大型计算机就会把每张打孔卡上的数据使用一种定长的 8 位编码保存,这种编码是 扩增二进式十进交换码 Extended Binary Coded Decimal Interchange Code ,简写为 EBCDIC 编码。它是一种二进制编码,可以追溯自早期打孔卡所使用的 BCDIDC 编码 —— 其 6 位编码使用低 4 位表示数字行,高 2 位表示空间行。程序员们在打孔卡上编写完程序后,会把卡片们交给计算机操作员,操作员们会把这些卡片放入 IBM 2540 ,再把打印结果交给程序员。那时的程序员大多都没有见过计算机长什么样。

程序员们真正能见到的是很多打孔机。029 型打孔机虽然不是计算机,但这并不意味着它不是一台复杂的机器。看看这个由 密歇根大学 University of Michigan 计算机中心在 1967 年制作的教学视频,你就能更好的理解使用一台 029 型打孔机是什么情形了。我会尽可能在这里总结这段视频,但如果你不去亲自看看的话,你会错过许多惊奇和感叹。

029 型打孔机的结构围绕着一个打孔卡穿过机器的 U 形轨道开始。使用打孔机时,右手边也就是 U 形轨道的右侧顶部是 进卡卡槽 hopper ,使用前通常在里面放入一叠未使用的打孔卡。虽然 029 型打孔机主要使用 80 列打孔卡,但在需要的情况下也可以使用更小号的打孔卡。在打孔机的使用过程中,打孔卡离开轨道右上端的进卡卡槽,顺着 U 形轨道移动并最终进入左上端的 出卡卡槽 stacker 。这一流程可以保证出卡卡槽中的打孔卡按打孔时的先后顺序排列。

029 型打孔机的开关在桌面下膝盖高度的位置。在开机后,连按两次 “ 装入 FEED ” 键让机器自动将打孔卡从进卡卡槽中取出并移动到机器内。 U 形轨道的底部是打孔机的核心区域,它由三个部分组成:右侧是等待区,中间是打孔操作区,左侧是阅读区。连按两次 “装入” 键,机器就会把一张打孔卡装入打孔机的打孔操作区,另一张打孔卡进入等待区。在打孔操作区上方有一个列数指示器来显示当前打孔所在的列的位置。这时,每按下一个按键,机器就会在打孔卡对应的位置打孔并在卡片的顶部打印按键对应的字符,随后将打孔卡向左移动一列。如果一张卡片的 80 列全部被打上了数据,这张卡片会被打孔操作区自动释放并进入阅读区,同时,一张新的打孔卡会被装入打孔操作区。如果没有打完全部的 80 列,可以使用 “ 释放 REL ” 键完成上面的操作。

在打孔卡上打印对应的字符这一设计让人很容易分辨出错误。但就像密歇根大学的视频中警告的那样,打孔卡上修正一个错误可不像擦掉一个打印的字符然后再写上一个新的那样容易,因为计算机只会根据卡片上的孔来读取信息。因为被打出的孔不能被 复原 unpunched ,所以并不能直接退回一列然后再打上一个新的字符。打出更多的孔也只能让这一列的组合变成一个无效字符。IBM 029 型打孔机上虽然有一个可以让打孔卡回退一列的退格按键,但这个按键被放置在机器上而非在键盘上。这样的设计也许是为了阻止这个按键的使用,因为实际上很少有用户需要这个功能。

实际上,只有废弃错误的打孔卡再在新的打孔卡上重新打孔这一种修正错误的方式。这就是阅读区的用武之处了。当你发现打孔卡上的第 68 列出错时,你需要在新的打孔卡上小心的给前 67 列重新打孔,然后给第 68 列打上正确的字母。另一种操作方式是把带有错误信息的打孔卡放在阅读区,同时在打孔操作区载入一张新的打孔卡,然后按下 “ 重复 DUP ” 按键直到列数指示器显示 68 列。这时按下正确的字符来修正错误。阅读区和重复按键使得 029 型打孔机很容易复制打孔卡上的内容。当然,这一功能的使用可能有各种各样的原因,但改错是最常见的。

(LCTT 译注:有一种说法是“补丁”这个用于对已经发布的软件进行修复的术语来源于对打孔纸带或打孔卡上打错的孔贴上补丁的做法。可能对于长长的一卷打孔纸带来说,由于个别字母的错误而整个废弃成本过高,会采用“补丁”的方式;而对于这种单张式的打孔卡来说,重新打印一张正确的更为方便。)

“重复”按键允许 029 型打孔机的操作员手动调用重复的函数。但是 029 型打孔机还可以设置为自动重复。当用于记录数据而不是编程时,这项功能十分有效。举个例子,当用打孔卡来记录大学生的信息时,每张卡片上都需要输入学生的宿舍楼的名字,如果发现所输入信息的学生都在同一栋楼,就可以使用 029 型打孔机的自动重复功能来完成宿舍楼名称的填写。

像这样的自动化操作可以通过 程序鼓 program drum 编程到 029 型打孔机里面。程序鼓就安装在打孔操作区上方的 U 形轨道中间部分的右上角。通过在打孔卡上写下程序,然后把打孔卡装入程序鼓中,就完成了一次给 029 型打孔机的编程任务。用户可以通过这种方式对打孔卡的每一列都按需要定义不同的自动化操作。029 型打孔机允许指定某些列重复上一张打孔卡相同位置的字符,这就是它能更快的输入学生信息的理由。它还允许指定某些列只能输入数字或者字母,指定特定的列为空或者到某一列时就直接跳过一整张打孔卡。编程鼓使它在打孔特定列有特殊含义的固定模式卡片时很容易。密歇根大学制作的另一个进阶教学视频包括了给 029 型打孔机编程的过程,如果你已经掌握了它的基础操作,就快去看看吧。

这会儿,无论你是否看了密歇根大学制作的视频,都会感叹打孔机的操作之简便。虽然修正错误的过程很乏味,但除此之外,操作一台打孔机并不像想象的那样复杂。我甚至可以想象打孔卡之间的无缝切换让 COBOL 和 FORTRAN 程序员忘记了他们的程序是打在不同的打孔卡上而不是写在一个连续的文本文件内。另一方面,思考一下打孔机是如何影响编程语言的发展也是很有趣的,虽然它仅仅是一台输入设备。结构化编程最终会出现并鼓励程序员把整个代码块视为一个整体,但可以想象打孔卡程序员们强调每一行的作用且难以认同结构化编程的场景。同时你能够理解他们为什么不把代码块闭合所使用的括号放在单独的一行,只是因为这样会浪费打孔卡。

现在,虽然没有人再使用打孔卡编程了,每个程序员都该试试这个,哪怕一次也好。或许你因此能够更好的理解 COBOL 和 FORTRAN 的历史,或许你就能体会到为什么每个人把 80 个字符作为长度限制的标注。

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


via: https://twobithistory.org/2018/06/23/ibm-029-card-punch.html

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

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


  1. “IBM 29 Card Punch,” IBM Archives, accessed June 23, 2018, https://www-03.ibm.com/ibm/history/exhibits/vintage/vintage_4506VV4002.html.
  2. IBM, IBM 2540 Component Description and Operation Procedures (Rochester, MN: IBM Product Publications, 1965), September 06, 2009, accessed June 23, 2018, http://bitsavers.informatik.uni-stuttgart.de/pdf/ibm/25xx/A21-9033-1_2540_Card_Punch_Component_Description_1965.pdf.

以前我和我的一些亲戚争论过计算机科学的学位值不值得读。当时我正在上大学,并要决定是不是该主修计算机。我姨和我表姐觉得我不应该主修计算机。她们承认知道如何编程肯定是很有用且对自己有利的一件事,但是她们认为计算机科学现在发展的如此迅速以至于我学的东西几乎马上就过时了。建议我更好是把编程作为辅业,选择一个基础原理可以受用终身的领域主修,比如经济学或物理学。

我知道我姨和我表姐说的不对,并决定主修计算机科学。(对不住啊!)平常人可能会觉得像计算机科学领域和软件工程专业每隔几年就完全和之前不一样了。其原因很容易理解。我们有了个人电脑,然后有了互联网,有了手机,之后还有了机器学习…… 科技总是在更新,支撑科技发展的原理和技能当然也在改变。当然,最惊人的是其实原理的改变竟然如此之小。我敢肯定,大多数人在知道了他们电脑里一些重要的软件的历史是多么久远时他们一定会深感震惊。当然我不是说那些刷版本号的浮夸软件 —— 我电脑上的 Firefox 浏览器副本,可能是我用的最多的软件,可能两周前就更新过。如果你看了比如 grep 的手册页,你就会发现它在 2010 年后就没有过更新了(至少在 MacOS 上如此)。初版 grep 是在 1974 年写就的,那时可以算是计算机世界的侏罗纪了。直到现在,人们(还有程序)仍然依赖 grep 来完成日常工作。

我姨和我表姐认为计算机技术就像一系列日渐精致的沙堡,在潮水抹净沙滩后新的沙堡完全取代旧的。但事实上,在很多领域上,我们都是不断积累能够解决问题的程序。我们可能不得不偶尔修改这些程序以避免软件无法使用,但大多数情况下我们都可以不修改。grep 是一个简单的程序,可以解决一个仍然存在的需求,所以它能够存活下来。 大多数应用程序编程都是在非常高的级别上完成的,它们建立在解决了旧问题的旧程序的金字塔上。 30 年或 40 年前的思路和概念,远非过时,在很多情况下它们依然在您的笔记本电脑上软件中存在着。

我想追溯这样的老程序自第一次写就以来改变了多少回很有趣。 cat 可能是所有 Unix 实用程序中最简单的,因此我们以它为例。Ken Thompson 于 1969 年编写了 cat 的原始实现。如果我告诉别人我的电脑上安装了个来自 1969 年的程序,这准确吗?我们电脑上的程序多大了?

感谢这种这种仓库,我们可以完整的看到 cat 自 1969 年后是如何发展的。我会先聚焦于可以算得上是我的 MacBook 上的 cat 的祖先的 cat 实现。随着我们从 Unix 上的第一版 cat 追踪到现在 MacOS 上的 cat,你会发现,这个程序被重写的次数比你想的还要多 —— 但是直到现在它运行的方式和五十年前多少是完全一致的。

研究 Unix

Ken Thompson 和 Dennis Ritchie 在 PDP 7 上开始写 Unix。那还是 1969 年,C 还没被发明出来,因此所有早期的 Unix 软件都是用 PDP 7 汇编实现的。他们使用的汇编种类是 Unix 特有的,Ken Thompson 在 DEC(PDP 7 的厂商)提供的汇编器之上加了些特性,实现了自己的汇编器。Thompson 的更改在最初的 Unix 程序员手册as(也就是汇编器)条目下均有所记录。

因此,最初的 cat 也是使用 PDP 7 汇编实现的。 我添加了一些注释,试图解释每条指令的作用,但除非你理解 Thompson 在编写汇编器时加的特性,否则程序仍然很难理解。在那些特性中有两个很重要:其一是 ; 这个字符可以在一行中用来分隔多条语句,它多出现于在使用 sys 指令时将系统调用的多个参数放在同一行上。其二是, Thompson 的汇编器支持使用 0 到 9 作为“临时标签”,这是在程序内可以重用的标签。因此。就如 Unix 程序员手册中所说:“对程序员的想象力和汇编程序的符号空间的要求都降低了”。在任何给定的指令内,你都可以使用 nfnb 来引用下一个或最近的临时标签 n。 例如,如果存在标记为 1: 的代码块,你就可以使用指令 jmp 1b 从下游代码跳回该块。 (但是你不使用 jmp 1f 的话就没法从上面的代码跳到这里。)

初版 cat 最有趣的就是它包含着我们应该认识的符号。有一块指令块标记为 getc,还有一个标记为 putc,可以看到这两个符号比 C 标准还古老。第一版的 cat 函数实际上已经包含了这两个函数的实现。该实现做了输入缓存,这样它就不需要一次只读写一个字母。

cat 的第一个版本并没有持续多久。 Ken Thompson 和 Dennis Ritchie 说服贝尔实验室购买了 PDP 11,这样他们就能够继续扩展和改进 Unix。 PDP 11 的指令集和之前不一样,因此必须重写 cat。 我也注释了这个第二版 cat。 它为新的指令集使用新的汇编程序助记符,并利用了 PDP 11 的各种寻址模式。(如果你对源代码中的括号和美元符号感到困惑,那是因为这些符号用于指示不同的寻址模式。)但它也使用 ; 字符和临时标签,和 cat 的第一个版本一样,这意味着当把 as 移植到 PDP 11 上时,必须要保留这些功能。

cat 的第二个版本比第一个版本简单得多。 它也更有 Unix 味儿,它不只是依靠参数列表,一旦没给参数列表,它将从 stdin 读取数据,这也就是今天 cat 仍在做的事情。 你也可以在此版本的 cat 中以 - 为参数,以表示它应该从stdin读取。

在 1973 年,为了准备发布第四版 Unix,大部分代码都用 C 语言重写了。但是 cat 似乎在之后一段时间内并没有使用 C 重写。 cat 的第一个 C 语言实现出现在第七版 Unix 中。 这个实现非常有趣,因为它很简单。 在所有以后的实现中,这个实现和在 K&R 的 C 语言教科书中用作教学示范的理想化 cat 最相似。这个程序的核心就是经典的两行:

while ((c = getc(fi)) != EOF)
 putchar(c);

当然实际代码要比这多一些,额外的代码主要是为了确保你没有在读/写同一个文件。另一个有趣的事情是,cat 的这一版实现只识别一个标志位 -u-u 标志可用于避免缓冲输入和输出,否则 cat 将以 512 字节为块进行输入输出。

BSD

在第七版 Unix 之后,Unix 出现了各种衍生品和分支。 MacOS 建立于 Darwin 之上,而 Darwin 又源自 伯克利软件分发版 Berkeley Software Distribution (BSD),因此 BSD 是我们最感兴趣的 Unix 分支。 BSD 最初只是 Unix 中的实用程序和附加组件的集合,但它最终成为了一个完整的操作系统。直到第四版 BSD,人称 4BSD,为一大堆新标志添加了支持之前,BSD 似乎还是依赖于最初的 cat 实现的。cat4BSD 实现 显然是从原始实现中衍生出来的,尽管它添加了一个新函数来实现由新标志触发的行为。已经在文件中使用的 fflg 变量(用于标记输入是从 stdin 还是文件读取的)的命名约定,被新添加的 nflgbflgvflgsflgeflgtflg 沿袭了下来,这些变量记录了在调用程序时是否使用了这些新标志。这些是最后一批添加到 cat 的命令行标志。如今 cat 的手册页列出了这些标志,没有其他的标志了,至少在 Mac OS 上是如此。 4BSD 于 1980 年发布,因此这套标志已有 38 年历史。

cat 最后一次被完全重写是在 BSD NET/2 上,其目的是通过替换 AT&T 发布的全部 Unix 源代码来规避许可证问题。BSD Net/2 在 1991 年发布。这一版本的 cat 是由 Kevin Fall 重写的。 Kevin Fall 于 1988 年毕业于加州大学伯克利分校并在下一年成为 计算机系统研究组 Computer Systems Research Group (CSRG)的组员,Fall 和我说当时使用 AT&T 代码的 Unix 工具被列在了 CSRG 的墙上,组员需要从中选出他们想要重写的工具; Fall 选了 cat 以及 mknod。 MacOS 系统内自带的 cat 实现源码的最上面还有着他的名字。他的这一版 cat,尽管平淡无奇,在今天还是被无数人使用着。

Fall 的原始 cat 实现 比我们迄今为止看到的版本都要长。 除了支持 -? 帮助标志外,它没有增加任何新功能。 从概念上讲,它与 4BSD 的实现非常相似。 它长是因为 Fall 将实现分为 “原始” 模式和 “加工” 模式。 “原始” 模式是 cat 的经典实现;它一个字符一个字符的打印文件。 “加工” 模式是带有所有 4BSD 命令行选项的 cat。 如此区别不无道理,但这么办也扩充了实现规模,因此乍一看其源码似乎比实际上更复杂。文件末尾还有一个奇特的错误处理函数,进一步地增加了实现的长度。

MacOS

在 2001 年,苹果发布了 MacOS X。这一发布对苹果意义重大。因为苹果用了多年的时间尝试以取代其现有的老旧操作系统(经典的 Mac OS),但是都失败了。 在 Mac OS X 之前苹果两次尝试在内部创建一个新的操作系统,但两者都无疾而终。 最后,苹果收购了史蒂夫·乔布斯的 NeXT 公司,后者开发了一个名为 NeXTSTEP 的操作系统和面向对象编程框架。 苹果将 NeXTSTEP 作为 Mac OS X 的基础。因为 NeXTSTEP 部分基于 BSD,使以 NeXTSTEP 为基础的 Mac OS X 的自然就把 BSD 系的代码直接带入苹果宇宙的中心。

因此,Mac OS X 的非常早期的第一个版本包含了从 NetBSD 项目中提取的 cat实现。如今仍保持开发的 NetBSD 最初是 386BSD 的分支,而后者又直接基于 BSD Net/2。所以 Mac OS X 里面的第一个 cat 的实现就是 Kevin Fall 的 cat。唯一改变的是,Fall 的错误处理函数 err()err.h 提供的 err() 函数取代了。 err.h 是 C 标准库的 BSD 扩展。

之后不久,这里的 cat 的 NetBSD 实现被换成了 FreeBSD 中的 cat 实现。 根据维基百科),苹果在 Mac OS X 10.3(Panther)中开始使用 FreeBSD 的实现而不是 NetBSD 的实现。但根据苹果自己开源的版本,cat 的 Mac OS X 实现在 2007 年发布的 Mac OS X 10.5(Leopard)之前没有被替换。苹果为 Leopard 替换的的 FreeBSD 实现与今天苹果计算机上的实现相同。截至 2018 年,2007 年以来的这个实现仍未被更新或修改。

所以 Mac OS 上的 cat 已经很老了。实际上,这一实现在 2007 年在 MacOS X 上露面两年前就被发布了。 这个 2005 年的修改 在 FreeBSD 的 Github 镜像中可见,是在苹果将其合并入 Mac OS X 前对 FreeBSD 的 cat 实现进行的最后一次更改。所以 Mac OS X 中的实现没有与 FreeBSD 的 cat 实现保持同步,它如今已经 13 岁了。对于软件修改了多少代码才能仍是算是同一软件这一话题有着旷日持久的争论。不过,在这种情况下,源文件自 2005 年以来根本没有变化。

现在 Mac OS 使用的 cat 实现与 Fall 1991 年为 BSD Net/2 版本编写的实现没有什么不同。最大的区别是添加了一个全新的功能来提供 Unix 域套接字支持。FreeBSD 开发人员似乎将 Fall 的 raw_args() 函数和 cook_args() 函数组合成一个名为scanfiles() 的函数。否则,程序的核心就仍是 Fall 的代码。

我问过 Fall 对编写了如今被数以百万计的苹果用户(直接或者间接通过依赖 cat 的某些程序)使用的 cat 实现有何感想。Fall,如今是一位顾问,也是最新版《TCP/IP 详解》的合著者,他说,当人们从了解他对 cat 所做的工作中收获颇丰时,他感到很惊讶。 Fall 在计算机领域有着悠久的职业生涯,曾参与许多备受瞩目的项目,但似乎很多人仍对他在 1989 年重写 cat 的那六个月的工作感到最为兴奋。

百年老程序

在宏伟的发明史中,计算机并不是一项古老的发明。我们已经习惯了百年的照片甚至是百年的视频短片。但是计算机程序不一样 —— 它们代表着高科技和新技术。至少,他们是现代的技术造出来的。随着计算行业的成熟,我们有朝一日会发现自己正在使用有着接近百年历史的程序吗?

计算机硬件可能会发生较大的变化,使得我们也许无法让现在编译的可执行文件在一个世纪后的硬件上运行。也许编程语言设计的进步让未来没有人能理解 C 语言,cat 将来也可能也被别的语言重写很久了。 (尽管 C 已经存在了五十年了,而且它似乎不会很快就被替换掉。)但除此之外,为什么不永远使用我们现在的 cat

我认为 cat 的历史表明,计算机科学中的一些想法确实非常持久。事实上,对于 cat,这个想法和程序本身都很古老。不准确地说,我的电脑上的 cat 来自 1969 年。但我也可以说我的计算机上的 cat 来自1989 年,当时 Fall 写了他的 cat 实现。许多其他软件也同样古老。因此,也许我们不应该把计算机科学和软件开发视为不断破坏现状和发明新事物的领域。我们的计算机系统是由诸多历史文物构建的。有时,我们可能会花费更多时间在理解和维护这些历史文物上,而不是花在编写新代码上。

如果你喜欢本文,你可能更喜欢两周来一篇更新!在推特上关注 @TwoBitHistory 或者订阅这个 RSS 源 以保证接受到新的文章。


via: https://twobithistory.org/2018/11/12/cat.html

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

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

GitHub 网站发布于 2008 年。如果你的软件工程师职业生涯跟我一样,也是晚于此时间的话,Git 可能是你用过的唯一版本控制软件。虽然其陡峭的学习曲线和不直观地用户界面时常会遭人抱怨,但不可否认的是,Git 已经成为学习版本控制的每个人的选择。Stack Overflow 2015 年进行的开发者调查显示,69.3% 的被调查者在使用 Git,几乎是排名第二的 Subversion 版本控制系统使用者数量的两倍。 1 2015 年之后,也许是因为 Git 太受欢迎了,大家对此话题不再感兴趣,所以 Stack Overflow 停止了关于开发人员使用的版本控制系统的问卷调查。

GitHub 的发布时间距离 Git 自身发布时间很近。2005 年,Linus Torvalds 发布了 Git 的首个版本。现在的年经一代开发者可能很难想象“版本控制软件”一词所代表的世界并不仅仅只有 Git,虽然这样的世界诞生的时间并不长。除了 Git 外,还有很多可供选择。那时,开源开发者较喜欢 Subversion,企业和视频游戏公司使用 Perforce (到如今有些仍在用),而 Linux 内核项目依赖于名为 BitKeeper 的版本控制系统。

其中一些系统,特别是 BitKeeper,会让年经一代的 Git 用户感觉很熟悉,上手也很快,但大多数相差很大。除了 BitKeeper,Git 之前的版本控制系统都是以不同的架构模型为基础运行的。《Version Control By Example》一书的作者 Eric Sink 在他的书中对版本控制进行了分类,按其说法,Git 属于第三代版本控制系统,而大多数 Git 的前身,即流行于二十世纪九零年代和二十一世纪早期的系统,都属于第二代版本控制系统。 2 第三代版本控制系统是分布式的,第二代是集中式。你们以前大概都听过 Git 被描述为一款“分布式”版本控制系统。我一直都不明白分布式/集中式之间的区别,随后自己亲自安装了一款第二代的集中式版本控件系统,并做了相关实验,至少明白了一些。

我安装的版本系统是 CVS。CVS,即 “ 并发版本系统 Concurrent Versions System ” 的缩写,是最初的第二代版本控制系统。大约十年间,它是最为流行的版本控制系统,直到 2000 年被 Subversion 所取代。即便如此,Subversion 被认为是 “更好的 CVS”,这更进一步突出了 CVS 在二十世纪九零年代的主导地位。

CVS 最早是由一位名叫 Dick Grune 的荷兰科学家在 1986 年开发的,当时有一个编译器项目,他正在寻找一种能与其学生合作的方法。 3 CVS 最初仅仅只是一个包装了 RCS( 修订控制系统 Revision Control System ) 的 Shell 脚本集合,Grune 想改进这个第一代的版本控制系统。 RCS 是按悲观锁模式工作的,这意味着两个程序员不可以同时处理同一个文件。需要编辑一个文件话,首先得向 RCS 系统请求一个排它锁,锁定此文件直到完成编辑,如果你想编辑的文件有人正在编辑,你就必须等待。CVS 在 RCS 基础上改进,并把悲观锁模型替换成乐观锁模型,迎来了第二代版本控制系统的时代。现在,程序员可以同时编辑同一个文件、合并编辑部分,随后解决合并冲突问题。(后来接管 CVS 项目的工程师 Brian Berliner 于 1990 年撰写了一篇非常易读的关于 CVS 创新的 论文。)

从这个意义上来讲,CVS 与 Git 并无差异,因为 Git 也是运行于乐观锁模式的,但也仅仅只有此点相似。实际上,Linus Torvalds 开发 Git 时,他的一个指导原则是 WWCVSND,即 “ CVS 不能做的 What Would CVS Not Do ”。每当他做决策时,他都会力争选择那些在 CVS 设计里没有使用的功能选项。 4 所以即使 CVS 要早于 Git 十多年,但它对 Git 的影响是反面的。

我非常喜欢折腾 CVS。我认为要弄明白为什么 Git 的分布式特性是对以前的版本控制系统的极大改善的话,除了折腾 CVS 外,没有更好的办法。因此,我邀请你跟我一起来一段激动人心的旅程,并在接下来的十分钟内了解下这个近十年来无人使用的软件。(可以看看文末“修正”部分)

CVS 入门

CVS 的安装教程可以在其 项目主页 上找到。MacOS 系统的话,可以使用 Homebrew 安装。

由于 CVS 是集中式的,所以它有客户端和服务端之区分,这种模式 Git 是没有的。两端分别有不同的可执行文件,其区别不太明显。但要开始使用 CVS 的话,即使只在你的本地机器上使用,也必须设置 CVS 的服务后端。

CVS 的后端,即所有代码的中央存储区,被叫做 存储库 repository 。在 Git 中每一个项目都有一个存储库,而 CVS 中一个存储库就包含所有的项目。尽管有办法保证一次只能访问一个项目,但一个中央存储库包含所有东西是改变不了的。

要在本地创建存储库的话,请运行 init 命令。你可以像如下所示在家目录创建,也可以在你本地的任何地方创建。

$ cvs -d ~/sandbox init

CVS 允许你将选项传递给 cvs 命令本身或 init 子命令。出现在 cvs 命令之后的选项默认是全局的,而出现在子命令之后的是子命令特有选项。上面所示例子中,-d 标志是全局选项。在这儿是告诉 CVS 我们想要创建存储库路径在哪里,但一般 -d 标志指的是我们想要使用的且已经存在的存储库位置。一直使用 -d 标志很单调乏味,所以可以设置 CVSROOT 环境变量来代替。

因为我们只是在本地操作,所以仅仅使用 -d 参考来传递路径就可以,但也可以包含个主机名。

此命令在你的家目录创建了一个名叫 sandbox 的目录。 如果你列出 sandbox 内容,会发现下面包含有名为 CVSROOT 的目录。请不要把此目录与我们的环境变量混淆,它保存存储库的管理文件。

恭喜! 你刚刚创建了第一个 CVS 存储库。

检入代码

假设你决定留存下自己喜欢的颜色清单。因为你是一个有艺术倾向但很健忘的人,所以你键入颜色列表清单,并保存到一个叫 favorites.txt 的文件中:

blue
orange
green

definitely not yellow

我们也假设你把文件保存到一个叫 colors 的目录中。现在你想要把喜欢的颜色列表清单置于版本控制之下,因为从现在起的五十年间你会回顾下,随着时间的推移自己的品味怎么变化,这件事很有意思。

为此,你必须将你的目录导入为新的 CVS 项目。可以使用 import 命令:

$ cvs -d ~/sandbox import -m "" colors colors initial
N colors/favorites.txt

No conflicts created by this import

这里我们再次使用 -d 标志来指定存储库的位置,其余的参数是传输给 import 子命令的。必须要提供一条消息,但这儿没必要,所以留空。下一个参数 colors,指定了存储库中新目录的名字,这儿给的名字跟检入的目录名称一致。最后的两个参数分别指定了 “vendor” 标签和 “release” 标签。我们稍后就会谈论标签。

我们刚将 colors 项目拉入 CVS 存储库。将代码引入 CVS 有很多种不同的方法,但这是 《Pragmatic Version Control Using CVS》 一书所推荐方法,这是一本关于 CVS 的程序员实用指导书籍。使用这种方法有点尴尬的就是你得重新 检出 check out 工作项目,即使已经存在有 colors 此项目了。不要使用该目录,首先删除它,然后从 CVS 中检出刚才的版本,如下示:

$ cvs -d ~/sandbox co colors
cvs checkout: Updating colors
U colors/favorites.txt

这个过程会创建一个新的目录,也叫做 colors。此目录里会发现你的源文件 favorites.txt,还有一个叫 CVS 的目录。这个 CVS 目录基本上与每个 Git 存储库的 .git 目录等价。

做出改动

准备旅行。

和 Git 一样,CVS 也有 status 命令:

$ cvs status
cvs status: Examining .
===================================================================
File: favorites.txt     Status: Up-to-date

   Working revision:    1.1.1.1 2018-07-06 19:27:54 -0400
   Repository revision: 1.1.1.1 /Users/sinclairtarget/sandbox/colors/favorites.txt,v
   Commit Identifier:   fD7GYxt035GNg8JA
   Sticky Tag:      (none)
   Sticky Date:     (none)
   Sticky Options:  (none)

到这儿事情开始陌生起来了。CVS 没有提交对象这一概念。如上示,有一个叫 “ 提交标识符 Commit Identifier ” 的东西,但这可能是一个较新版本的标识,在 2003 年出版的《Pragmatic Version Control Using CVS》一书中并没有提到 “提交标识符” 这个概念。 (CVS 的最新版本于 2008 年发布的。 5

在 Git 中,我们所谈论某文件版本其实是在谈论如 commit 45de392 相关的东西,而 CVS 中文件是独立版本化的。文件的第一个版本为 1.1 版本,下一个是 1.2 版本,依此类推。涉及分支时,会在后面添加扩展数字。因此你会看到如上所示的 1.1.1.1 的内容,这就是示例的版本号,即使我们没有创建分支,似乎默认的会给加上。

一个项目中会有很多的文件和很多次的提交,如果你运行 cvs log 命令(等同于 git log),会看到每个文件提交历史信息。同一个项目中,有可能一个文件处于 1.2 版本,一个文件处于 1.14 版本。

继续,我们对 1.1 版本的 favorites.txt 文件做些修改(LCTT 译注:原文此处示例有误):

blue
orange
green
cyan

definitely not yellow

修改完成,就可以运行 cvs diff 来看看 CVS 发生了什么:

$ cvs diff
cvs diff: Diffing .
Index: favorites.txt
===================================================================
RCS file: /Users/sinclairtarget/sandbox/colors/favorites.txt,v
retrieving revision 1.1.1.1
diff -r1.1.1.1 favorites.txt
3a4
> cyan

CVS 识别出我们我在文件中添加了一个包含颜色 “cyan” 的新行。(实际上,它说我们已经对 “RCS” 文件进行了更改;你可以看到,CVS 底层使用的还是 RCS。) 此差异指的是当前工作目录中的 favorites.txt 副本与存储库中 1.1.1.1 版本的文件之间的差异。

为了更新存储库中的版本,我们必须提交更改。Git 中,这个操作要好几个步骤。首先,暂存此修改,使其在索引中出现,然后提交此修改,最后,为了使此修改让其他人可见,我们必须把此提交推送到源存储库中。

而 CVS 中,只需要运行 cvs commit 命令就搞定一切。CVS 会汇集它所找到的变化,然后把它们放到存储库中:

$ cvs commit -m "Add cyan to favorites."
cvs commit: Examining .
/Users/sinclairtarget/sandbox/colors/favorites.txt,v <-- favorites.txt
new revision: 1.2; previous revision: 1.1

我已经习惯了 Git,所以这种操作会让我感到十分恐惧。因为没有变更暂存区的机制,工作目录下任何你动过的东西都会一股脑给提交到公共存储库中。你有过因为不爽,私下里重写了某个同事不佳的函数实现,但仅仅只是自我宣泄一下并不想让他知道的时候吗?如果不小心提交上去了,就太糟糕了,他会认为你是个混蛋。在推送它们之前,你也不能对提交进行编辑,因为提交就是推送。还是你愿意花费 40 分钟的时间来反复运行 git rebase -i 命令,以使得本地提交历史记录跟数学证明一样清晰严谨?很遗憾,CVS 里不支持,结果就是,大家都会看到你没有先写测试用例。

不过,到现在我终于理解了为什么那么多人都觉得 Git 没必要搞那么复杂。对那些早已经习惯直接 cvs commit 的人来说,进行暂存变更和推送变更操作确实是毫无意义的差事。

人们常谈论 Git 是一个 “分布式” 系统,其中分布式与非分布式的主要区别为:在 CVS 中,无法进行本地提交。提交操作就是向中央存储库提交代码,所以没有网络连接,就无法执行操作,你本地的那些只是你的工作目录而已;在 Git 中,会有一个完完全全的本地存储库,所以即使断网了也可以无间断执行提交操作。你还可以编辑那些提交、回退、分支,并选择你所要的东西,没有任何人会知道他们必须知道的之外的东西。

因为提交是个大事,所以 CVS 用户很少做提交。提交会包含很多的内容修改,就像如今我们能在一个含有十次提交的拉取请求中看到的一样多。特别是在提交触发了 CI 构建和自动测试程序时如此。

现在我们运行 cvs status,会看到产生了文件的新版本:

$ cvs status
cvs status: Examining .
===================================================================
File: favorites.txt     Status: Up-to-date

   Working revision:    1.2 2018-07-06 21:18:59 -0400
   Repository revision: 1.2 /Users/sinclairtarget/sandbox/colors/favorites.txt,v
   Commit Identifier:   pQx5ooyNk90wW8JA
   Sticky Tag:      (none)
   Sticky Date:     (none)
   Sticky Options:  (none)

合并

如上所述,在 CVS 中,你可以同时编辑其他人正在编辑的文件。这是 CVS 对 RCS 的重大改进。当需要将更改的部分重新组合在一起时会发生什么?

假设你邀请了一些朋友来将他们喜欢的颜色添加到你的列表中。在他们添加的时候,你确定了不再喜欢绿色,然后把它从列表中删除。

当你提交更新的时候,会发现 CVS 报出了个问题:

$ cvs commit -m "Remove green"
cvs commit: Examining .
cvs commit: Up-to-date check failed for `favorites.txt'
cvs [commit aborted]: correct above errors first!

这看起来像是朋友们首先提交了他们的变化。所以你的 favorites.txt 文件版本没有更新到存储库中的最新版本。此时运行 cvs status 就可以看到,本地的 favorites.txt 文件副本有一些本地变更且是 1.2 版本的,而存储库上的版本号是 1.3,如下示:

$ cvs status
cvs status: Examining .
===================================================================
File: favorites.txt     Status: Needs Merge

   Working revision:    1.2 2018-07-07 10:42:43 -0400
   Repository revision: 1.3 /Users/sinclairtarget/sandbox/colors/favorites.txt,v
   Commit Identifier:   2oZ6n0G13bDaldJA
   Sticky Tag:      (none)
   Sticky Date:     (none)
   Sticky Options:  (none)

你可以运行 cvs diff 来了解 1.2 版本与 1.3 版本的确切差异:

$ cvs diff -r HEAD favorites.txt
Index: favorites.txt
===================================================================
RCS file: /Users/sinclairtarget/sandbox/colors/favorites.txt,v
retrieving revision 1.3
diff -r1.3 favorites.txt
3d2
< green
7,10d5
<
< pink
< hot pink
< bubblegum pink

看来我们的朋友是真的喜欢粉红色,但好在他们编辑的是此文件的不同部分,所以很容易地合并此修改。跟 git pull 类似,只要运行 cvs update 命令,CVS 就可以为我们做合并操作,如下示:

$ cvs update
cvs update: Updating .
RCS file: /Users/sinclairtarget/sandbox/colors/favorites.txt,v
retrieving revision 1.2
retrieving revision 1.3
Merging differences between 1.2 and 1.3 into favorites.txt
M favorites.txt

此时查看 favorites.txt 文件内容的话,你会发现你的朋友对文件所做的更改已经包含进去了,你的修改也在里面。现在你可以自由的提交文件了,如下示:

$ cvs commit
cvs commit: Examining .
/Users/sinclairtarget/sandbox/colors/favorites.txt,v <-- favorites.txt
new revision: 1.4; previous revision: 1.3

最终的结果就跟在 Git 中运行 git pull --rebase 一样。你的修改是添加在你朋友的修改之后的,所以没有 “合并提交” 这操作。

某些时候,对同一文件的修改可能导致冲突。例如,如果你的朋友把 “green” 修改成 “olive”,同时你完全删除 “green”,就会出现冲突。CVS 早期的时候,正是这种情况导致人们担心 CVS 不安全,而 RCS 的悲观锁机制可以确保此情况永不会发生。但 CVS 提供了一个安全保障机制,可以确保不会自动的覆盖任何人的修改。因此,当运行 cvs update 的时候,你必须告诉 CVS 想要保留哪些修改才能继续下一步操作。CVS 会标记文件的所有变更,这跟 Git 检测到合并冲突时所做的方式一样,然后,你必须手工编辑文件,选择需要保留的变更进行合并。

这儿需要注意的有趣事情就是在进行提交之前必须修复并合并冲突。这是 CVS 集中式特性的另一个结果。而在 Git 里,在推送本地的提交内容之前,你都不用担心合并冲突问题。

标记与分支

由于 CVS 没有易于寻址的提交对象,因此对变更集合进行分组的唯一方法就是对于特定的工作目录状态打个标记。

创建一个标记是很容易的:

$ cvs tag VERSION_1_0
cvs tag: Tagging .
T favorites.txt

稍后,运行 cvs update 命令并把标签传输给 -r 标志就可以把文件恢复到此状态,如下示:

$ cvs update -r VERSION_1_0
cvs update: Updating .
U favorites.txt

因为你需要一个标记来回退到早期的工作目录状态,所以 CVS 鼓励创建大量的抢先标记。例如,在重大的重构之前,你可以创建一个 BEFORE_REFACTOR_01 标记,如果重构出错,就可以使用此标记回退。你如果想生成整个项目的差异文件的话,也可以使用标记。基本上,如今我们惯常使用提交的哈希值完成的事情都必须在 CVS 中提前计划,因为你必须首先有个标签才行。

可以在 CVS 中创建分支。分支只是一种特殊的标记,如下示:

$ cvs rtag -b TRY_EXPERIMENTAL_THING colors
cvs rtag: Tagging colors

这命令仅仅只是创建了分支(每个人都这样觉得吧),所以还需要使用 cvs update 命令来切换分支,如下示:

$ cvs update -r TRY_EXPERIMENTAL_THING

上面的命令就会把你的当前工作目录切换到新的分支,但《Pragmatic Version Control Using CVS》一书实际上是建议创建一个新的目录来房子你的新分支。估计,其作者发现在 CVS 里切换目录要比切换分支来得更简单吧。

此书也建议不要从现有分支创建分支,而只在主线分支(Git 中被叫做 master)上创建分支。一般来说,分支在 CVS 中主认为是 “高级” 技能。而在 Git 中,你几乎可以任性创建新分支,但 CVS 中要在真正需要的时候才能创建,比如发布项目。

稍后可以使用 cvs update-j 标志将分支合并回主线:

$ cvs update -j TRY_EXPERIMENTAL_THING

感谢历史上的贡献者

2007 年,Linus Torvalds 在 Google 进行了一场关于 Git 的 演讲。当时 Git 是很新的东西,整场演讲基本上都是在说服满屋子都持有怀疑态度的程序员们:尽管 Git 是如此的与众不同,也应该使用 Git。如果没有看过这个视频的话,我强烈建议你去看看。Linus 是个有趣的演讲者,即使他有些傲慢。他非常出色地解释了为什么分布式的版本控制系统要比集中式的优秀。他的很多评论是直接针对 CVS 的。

Git 是一个 相当复杂的工具。学习起来是一个令人沮丧的经历,但也不断的给我惊喜:Git 还能做这样的事情。相比之下,CVS 简单明了,但是,许多我们认为理所当然的操作都做不了。想要对 Git 的强大功能和灵活性有全新的认识的话,就回过头来用用 CVS 吧,这是种很好的学习方式。这很好的诠释了为什么理解软件的开发历史可以让人受益匪浅。重拾过期淘汰的工具可以让我们理解今天所使用的工具后面所隐藏的哲理。

如果你喜欢此博文的话,每两周会有一次更新!请在 Twitter 上关注 @TwoBitHistory 或都通过 RSS feed 订阅,新博文出来会有通知。

修正

有人告诉我,有很多组织企业,特别是像做医疗设备软件等这种规避风险类的企业,仍在使用 CVS。这些企业中的程序员通过使用一些小技巧来解决 CVS 的限制,例如为几乎每个更改创建一个新分支以避免直接提交给 HEAD。 (感谢 Michael Kohne 指出这一点。)

(题图:plasticscm


via: https://twobithistory.org/2018/07/07/cvs.html

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

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


  1. “2015 Developer Survey,” Stack Overflow, accessed July 7, 2018, https://insights.stackoverflow.com/survey/2015#tech-sourcecontrol.
  2. Eric Sink, “A History of Version Control,” Version Control By Example, 2011, accessed July 7, 2018, https://ericsink.com/vcbe/html/history_of_version_control.html.
  3. Dick Grune, “Concurrent Versions System CVS,” dickgrune.com, accessed July 7, 2018, https://dickgrune.com/Programs/CVS.orig/#History.
  4. “Tech Talk: Linus Torvalds on Git,” YouTube, May 14, 2007, accessed July 7, 2018, https://www.youtube.com/watch?v=4XpnKHJAok8.
  5. “Concurrent Versions System - News,” Savannah, accessed July 7, 2018, http://savannah.nongnu.org/news/?group=cvs.

当程序员们谈论各类编程语言的相对优势时,他们通常会采用相当平淡的措词,就好像这些语言是一条工具带上的各种工具似的 —— 有适合写操作系统的,也有适合把其它程序黏在一起来完成特殊工作的。这种讨论方式非常合理;不同语言的能力不同。不声明特定用途就声称某门语言比其他语言更优秀只能导致侮辱性的无用争论。

但有一门语言似乎受到和用途无关的特殊尊敬:那就是 Lisp。即使是恨不得给每个说出形如“某某语言比其他所有语言都好”这类话的人都来一拳的键盘远征军们,也会承认 Lisp 处于另一个层次。 Lisp 超越了用于评判其他语言的实用主义标准,因为普通程序员并不使用 Lisp 编写实用的程序 —— 而且,多半他们永远也不会这么做。然而,人们对 Lisp 的敬意是如此深厚,甚至于到了这门语言会时而被加上神话属性的程度。

大家都喜欢的网络漫画合集 xkcd 就至少在两组漫画中如此描绘过 Lisp:其中一组漫画中,某人得到了某种 Lisp 启示,而这好像使他理解了宇宙的基本构架。

另一组漫画中,一个穿着长袍的老程序员给他的徒弟递了一沓圆括号,说这是“文明时代的优雅武器”,暗示着 Lisp 就像原力那样拥有各式各样的神秘力量。

另一个绝佳例子是 Bob Kanefsky 的滑稽剧插曲,《上帝就在人间》。这部剧叫做《永恒之火》,撰写于 1990 年代中期;剧中描述了上帝必然是使用 Lisp 创造世界的种种原因。完整的歌词可以在 GNU 幽默合集中找到,如下是一段摘抄:

因为上帝用祂的 Lisp 代码

让树叶充满绿意。

分形的花儿和递归的根:

我见过的奇技淫巧之中没什么比这更可爱。

当我对着雪花深思时,

从未见过两片相同的,

我知道,上帝偏爱那一门

名字是四个字母的语言。

(LCTT 译注:“四个字母”,参见:四字神名,致谢 no1xsyzy

以下这句话我实在不好在人前说;不过,我还是觉得,这样一种 “Lisp 是奥术魔法”的文化模因实在是有史以来最奇异、最迷人的东西。Lisp 是象牙塔的产物,是人工智能研究的工具;因此,它对于编程界的俗人而言总是陌生的,甚至是带有神秘色彩的。然而,当今的程序员们开始怂恿彼此,“在你死掉之前至少试一试 Lisp”,就像这是一种令人恍惚入迷的致幻剂似的。尽管 Lisp 是广泛使用的编程语言中第二古老的(只比 Fortran 年轻一岁) 1 ,程序员们也仍旧在互相怂恿。想象一下,如果你的工作是为某种组织或者团队推广一门新的编程语言的话,忽悠大家让他们相信你的新语言拥有神力难道不是绝佳的策略吗?—— 但你如何能够做到这一点呢?或者,换句话说,一门编程语言究竟是如何变成人们口中“隐晦知识的载体”的呢?

Lisp 究竟是怎么成为这样的?

Byte 杂志封面,1979年八月。

Byte 杂志封面,1979年八月。

理论 A :公理般的语言

Lisp 的创造者 约翰·麦卡锡 John McCarthy 最初并没有想过把 Lisp 做成优雅、精炼的计算法则结晶。然而,在一两次运气使然的深谋远虑和一系列优化之后,Lisp 的确变成了那样的东西。 保罗·格雷厄姆 Paul Graham (我们一会儿之后才会聊到他)曾经这么写道, 麦卡锡通过 Lisp “为编程作出的贡献就像是欧几里得对几何学所做的贡献一般” 2 。人们可能会在 Lisp 中看出更加隐晦的含义 —— 因为麦卡锡创造 Lisp 时使用的要素实在是过于基础,基础到连弄明白他到底是创造了这门语言、还是发现了这门语言,都是一件难事。

最初, 麦卡锡产生要造一门语言的想法,是在 1956 年的 达特茅斯人工智能夏季研究项目 Darthmouth Summer Research Project on Artificial Intelligence 上。夏季研究项目是个持续数周的学术会议,直到现在也仍旧在举行;它是此类会议之中最早开始举办的会议之一。 麦卡锡当初还是个达特茅斯的数学助教,而“ 人工智能 artificial intelligence (AI)”这个词事实上就是他建议举办该会议时发明的 3 。在整个会议期间大概有十人参加 4 。他们之中包括了 艾伦·纽厄尔 Allen Newell 赫伯特·西蒙 Herbert Simon ,两名隶属于 兰德公司 RAND Corporation 卡内基梅隆大学 Carnegie Mellon 的学者。这两人不久之前设计了一门语言,叫做 IPL。

当时,纽厄尔和西蒙正试图制作一套能够在命题演算中生成证明的系统。两人意识到,用电脑的原生指令集编写这套系统会非常困难;于是他们决定创造一门语言——他们的原话是“ 伪代码 pseudo-code ”,这样,他们就能更加轻松自然地表达这台“ 逻辑理论机器 Logic Theory Machine ”的底层逻辑了 5 。这门语言叫做 IPL,即“ 信息处理语言 Information Processing Language ”;比起我们现在认知中的编程语言,它更像是一种高层次的汇编语言方言。 纽厄尔和西蒙提到,当时人们开发的其它“伪代码”都抓着标准数学符号不放 —— 也许他们指的是 Fortran 6 ;与此不同的是,他们的语言使用成组的符号方程来表示命题演算中的语句。通常,用 IPL 写出来的程序会调用一系列的汇编语言宏,以此在这些符号方程列表中对表达式进行变换和求值。

麦卡锡认为,一门实用的编程语言应该像 Fortran 那样使用代数表达式;因此,他并不怎么喜欢 IPL 7 。然而,他也认为,在给人工智能领域的一些问题建模时,符号列表会是非常好用的工具 —— 而且在那些涉及演绎的问题上尤其有用。麦卡锡的渴望最终被诉诸行动;他要创造一门代数的列表处理语言 —— 这门语言会像 Fortran 一样使用代数表达式,但拥有和 IPL 一样的符号列表处理能力。

当然,今日的 Lisp 可不像 Fortran。在会议之后的几年中,麦卡锡关于“理想的列表处理语言”的见解似乎在逐渐演化。到 1957 年,他的想法发生了改变。他那时候正在用 Fortran 编写一个能下国际象棋的程序;越是长时间地使用 Fortran ,麦卡锡就越确信其设计中存在不当之处,而最大的问题就是尴尬的 IF 声明 8 。为此,他发明了一个替代品,即条件表达式 true;这个表达式会在给定的测试通过时返回子表达式 A ,而在测试未通过时返回子表达式 B而且,它只会对返回的子表达式进行求值。在 1958 年夏天,当麦卡锡设计一个能够求导的程序时,他意识到,他发明的 true 条件表达式让编写递归函数这件事变得更加简单自然了 9 。也是这个求导问题让麦卡锡创造了 maplist 函数;这个函数会将其它函数作为参数并将之作用于指定列表的所有元素 10 。在给项数多得叫人抓狂的多项式求导时,它尤其有用。

然而,以上的所有这些,在 Fortran 中都是没有的;因此,在 1958 年的秋天,麦卡锡请来了一群学生来实现 Lisp。因为他那时已经成了一名麻省理工助教,所以,这些学生可都是麻省理工的学生。当麦卡锡和学生们最终将他的主意变为能运行的代码时,这门语言得到了进一步的简化。这之中最大的改变涉及了 Lisp 的语法本身。最初,麦卡锡在设计语言时,曾经试图加入所谓的 “M 表达式”;这是一层语法糖,能让 Lisp 的语法变得类似于 Fortran。虽然 M 表达式可以被翻译为 S 表达式 —— 基础的、“用圆括号括起来的列表”,也就是 Lisp 最著名的特征 —— 但 S 表达式事实上是一种给机器看的低阶表达方法。唯一的问题是,麦卡锡用方括号标记 M 表达式,但他的团队在麻省理工使用的 IBM 026 键盘打孔机的键盘上根本没有方括号 11 。于是 Lisp 团队坚定不移地使用着 S 表达式,不仅用它们表示数据列表,也拿它们来表达函数的应用。麦卡锡和他的学生们还作了另外几样改进,包括将数学符号前置;他们也修改了内存模型,这样 Lisp 实质上就只有一种数据类型了 12

到 1960 年,麦卡锡发表了他关于 Lisp 的著名论文,《用符号方程表示的递归函数及它们的机器计算》。那时候,Lisp 已经被极大地精简,而这让麦卡锡意识到,他的作品其实是“一套优雅的数学系统”,而非普通的编程语言 13 。他后来这么写道,对 Lisp 的许多简化使其“成了一种描述可计算函数的方式,而且它比图灵机或者一般情况下用于递归函数理论的递归定义更加简洁” 14 。在他的论文中,他不仅使用 Lisp 作为编程语言,也将它当作一套用于研究递归函数行为方式的表达方法。

通过“从一小撮规则中逐步实现出 Lisp”的方式,麦卡锡将这门语言介绍给了他的读者。后来,保罗·格雷厄姆在短文《 Lisp 之根 The Roots of Lisp 》中用更易读的语言回顾了麦卡锡的步骤。格雷厄姆只用了七种原始运算符、两种函数写法,以及使用原始运算符定义的六个稍微高级一点的函数来解释 Lisp。毫无疑问,Lisp 的这种只需使用极少量的基本规则就能完整说明的特点加深了其神秘色彩。格雷厄姆称麦卡锡的论文为“使计算公理化”的一种尝试 15 。我认为,在思考 Lisp 的魅力从何而来时,这是一个极好的切入点。其它编程语言都有明显的人工构造痕迹,表现为 Whiletypedefpublic static void 这样的关键词;而 Lisp 的设计却简直像是纯粹计算逻辑的鬼斧神工。Lisp 的这一性质,以及它和晦涩难懂的“递归函数理论”的密切关系,使它具备了获得如今声望的充分理由。

理论 B:属于未来的机器

Lisp 诞生二十年后,它成了著名的《 黑客词典 Hacker’s Dictionary 》中所说的,人工智能研究的“母语”。Lisp 在此之前传播迅速,多半是托了语法规律的福 —— 不管在怎么样的电脑上,实现 Lisp 都是一件相对简单直白的事。而学者们之后坚持使用它乃是因为 Lisp 在处理符号表达式这方面有巨大的优势;在那个时代,人工智能很大程度上就意味着符号,于是这一点就显得十分重要。在许多重要的人工智能项目中都能见到 Lisp 的身影。这些项目包括了 SHRDLU 自然语言程序Macsyma 代数系统ACL2 逻辑系统

然而,在 1970 年代中期,人工智能研究者们的电脑算力开始不够用了。PDP-10 就是一个典型。这个型号在人工智能学界曾经极受欢迎;但面对这些用 Lisp 写的 AI 程序,它的 18 位地址空间一天比一天显得吃紧 16 。许多的 AI 程序在设计上可以与人互动。要让这些既极度要求硬件性能、又有互动功能的程序在分时系统上优秀发挥,是很有挑战性的。麻省理工的 彼得·杜奇 Peter Deutsch 给出了解决方案:那就是针对 Lisp 程序来特别设计电脑。就像是我那关于 Chaosnet 的上一篇文章所说的那样,这些 Lisp 计算机 Lisp machines 会给每个用户都专门分配一个为 Lisp 特别优化的处理器。到后来,考虑到硬核 Lisp 程序员的需求,这些计算机甚至还配备上了完全由 Lisp 编写的开发环境。在当时那样一个小型机时代已至尾声而微型机的繁盛尚未完全到来的尴尬时期,Lisp 计算机就是编程精英们的“高性能个人电脑”。

有那么一会儿,Lisp 计算机被当成是未来趋势。好几家公司雨后春笋般出现,追着赶着要把这项技术商业化。其中最成功的一家叫做 Symbolics,由麻省理工 AI 实验室的前成员创立。上世纪八十年代,这家公司生产了所谓的 3600 系列计算机,它们当时在 AI 领域和需要高性能计算的产业中应用极广。3600 系列配备了大屏幕、位图显示、鼠标接口,以及强大的图形与动画软件。它们都是惊人的机器,能让惊人的程序运行起来。例如,之前在推特上跟我聊过的机器人研究者 Bob Culley,就能用一台 1985 年生产的 Symbolics 3650 写出带有图形演示的寻路算法。他向我解释说,在 1980 年代,位图显示和面向对象编程(能够通过 Flavors 扩展)在 Lisp 计算机上使用)都刚刚出现。Symbolics 站在时代的最前沿。

Bob Culley 的寻路程序。

Bob Culley 的寻路程序。

而以上这一切导致 Symbolics 的计算机奇贵无比。在 1983 年,一台 Symbolics 3600 能卖 111,000 美金 16 。所以,绝大部分人只可能远远地赞叹 Lisp 计算机的威力和操作员们用 Lisp 编写程序的奇妙技术。不止他们赞叹,从 1979 年到 1980 年代末,Byte 杂志曾经多次提到过 Lisp 和 Lisp 计算机。在 1979 年八月发行的、关于 Lisp 的一期特别杂志中,杂志编辑激情洋溢地写道,麻省理工正在开发的计算机配备了“大坨大坨的内存”和“先进的操作系统” 17 ;他觉得,这些 Lisp 计算机的前途是如此光明,以至于它们的面世会让 1978 和 1977 年 —— 诞生了 Apple II、Commodore PET 和 TRS-80 的两年 —— 显得黯淡无光。五年之后,在 1985 年,一名 Byte 杂志撰稿人描述了为“复杂精巧、性能强悍的 Symbolics 3670”编写 Lisp 程序的体验,并力劝读者学习 Lisp,称其为“绝大数人工智能工作者的语言选择”,和将来的通用编程语言 18

我问过 保罗·麦克琼斯 Paul McJones (他在 山景城 Mountain View 计算机历史博物馆 Computer History Museum 做了许多 Lisp 的保护工作),人们是什么时候开始将 Lisp 当作高维生物的赠礼一样谈论的呢?他说,这门语言自有的性质毋庸置疑地促进了这种现象的产生;然而,他也说,Lisp 上世纪六七十年代在人工智能领域得到的广泛应用,很有可能也起到了作用。当 1980 年代到来、Lisp 计算机进入市场时,象牙塔外的某些人由此接触到了 Lisp 的能力,于是传说开始滋生。时至今日,很少有人还记得 Lisp 计算机和 Symbolics 公司;但 Lisp 得以在八十年代一直保持神秘,很大程度上要归功于它们。

理论 C:学习编程

1985 年,两位麻省理工的教授, 哈尔·阿伯尔森 Harold Hal Abelson 杰拉尔德·瑟斯曼 Gerald Sussman ,外加瑟斯曼的妻子 朱莉·瑟斯曼 Julie Sussman ,出版了一本叫做《 计算机程序的构造和解释 Structure and Interpretation of Computer Programs 》的教科书。这本书用 Scheme(一种 Lisp 方言)向读者们示范了如何编程。它被用于教授麻省理工入门编程课程长达二十年之久。出于直觉,我认为 SICP(这本书的名字通常缩写为 SICP)倍增了 Lisp 的“神秘要素”。SICP 使用 Lisp 描绘了深邃得几乎可以称之为哲学的编程理念。这些理念非常普适,可以用任意一种编程语言展现;但 SICP 的作者们选择了 Lisp。结果,这本阴阳怪气、卓越不凡、吸引了好几代程序员(还成了一种奇特的模因)的著作臭名远扬之后,Lisp 的声望也顺带被提升了。Lisp 已不仅仅是一如既往的“麦卡锡的优雅表达方式”;它现在还成了“向你传授编程的不传之秘的语言”。

SICP 究竟有多奇怪这一点值得好好说;因为我认为,时至今日,这本书的古怪之处和 Lisp 的古怪之处是相辅相成的。书的封面就透着一股古怪。那上面画着一位朝着桌子走去,准备要施法的巫师或者炼金术士。他的一只手里抓着一副测径仪 —— 或者圆规,另一只手上拿着个球,上书“eval”和“apply”。他对面的女人指着桌子;在背景中,希腊字母 λ (lambda)漂浮在半空,释放出光芒。

SICP 封面上的画作

SICP 封面上的画作。

说真的,这上面画的究竟是怎么一回事?为什么桌子会长着动物的腿?为什么这个女人指着桌子?墨水瓶又是干什么用的?我们是不是该说,这位巫师已经破译了宇宙的隐藏奥秘,而所有这些奥秘就蕴含在 eval/apply 循环和 Lambda 演算之中?看似就是如此。单单是这张图片,就一定对人们如今谈论 Lisp 的方式产生了难以计量的影响。

然而,这本书的内容通常并不比封面正常多少。SICP 跟你读过的所有计算机科学教科书都不同。在引言中,作者们表示,这本书不只教你怎么用 Lisp 编程 —— 它是关于“现象的三个焦点:人的心智、复数的计算机程序,和计算机”的作品 19 。在之后,他们对此进行了解释,描述了他们对如下观点的坚信:编程不该被当作是一种计算机科学的训练,而应该是“ 程序性认识论 procedural epistemology ”的一种新表达方式 20 。程序是将那些偶然被送入计算机的思想组织起来的全新方法。这本书的第一章简明地介绍了 Lisp,但是之后的绝大部分都在讲述更加抽象的概念。其中包括了对不同编程范式的讨论,对于面向对象系统中“时间”和“一致性”的讨论;在书中的某一处,还有关于通信的基本限制可能会如何带来同步问题的讨论 —— 而这些基本限制在通信中就像是光速不变在相对论中一样关键 21 。都是些高深难懂的东西。

以上这些并不是说这是本糟糕的书;这本书其实棒极了。在我读过的所有作品中,这本书对于重要的编程理念的讨论是最为深刻的;那些理念我琢磨了很久,却一直无力用文字去表达。一本入门编程教科书能如此迅速地开始描述面向对象编程的根本缺陷,和函数式语言“将可变状态降到最少”的优点,实在是一件让人印象深刻的事。而这种描述之后变为了另一种震撼人心的讨论:某种(可能类似于今日的 RxJS 的)流范式能如何同时具备两者的优秀特性。SICP 用和当初麦卡锡的 Lisp 论文相似的方式提纯出了高级程序设计的精华。你读完这本书之后,会立即想要将它推荐给你的程序员朋友们;如果他们找到这本书,看到了封面,但最终没有阅读的话,他们就只会记住长着动物腿的桌子上方那神秘的、根本的、给予魔法师特殊能力的、写着 eval/apply 的东西。话说回来,书上这两人的鞋子也让我印象颇深。

然而,SICP 最重要的影响恐怕是,它将 Lisp 由一门怪语言提升成了必要的教学工具。在 SICP 面世之前,人们互相推荐 Lisp,以学习这门语言为提升编程技巧的途径。1979 年的 Byte 杂志 Lisp 特刊印证了这一事实。之前提到的那位编辑不仅就麻省理工的新 Lisp 计算机大书特书,还说,Lisp 这门语言值得一学,因为它“代表了分析问题的另一种视角” 22 。但 SICP 并未只把 Lisp 作为其它语言的陪衬来使用;SICP 将其作为入门语言。这就暗含了一种论点,那就是,Lisp 是最能把握计算机编程基础的语言。可以认为,如今的程序员们彼此怂恿“在死掉之前至少试试 Lisp”的时候,他们很大程度上是因为 SICP 才这么说的。毕竟,编程语言 Brainfuck 想必同样也提供了“分析问题的另一种视角”;但人们学习 Lisp 而非学习 Brainfuck,那是因为他们知道,前者的那种 Lisp 视角在二十年中都被看作是极其有用的,有用到麻省理工在给他们的本科生教其它语言之前,必然会先教 Lisp。

Lisp 的回归

在 SICP 出版的同一年, 本贾尼·斯特劳斯特卢普 Bjarne Stroustrup 发布了 C++ 语言的首个版本,它将面向对象编程带到了大众面前。几年之后,Lisp 计算机的市场崩盘,AI 寒冬开始了。在下一个十年的变革中, C++ 和后来的 Java 成了前途无量的语言,而 Lisp 被冷落,无人问津。

理所当然地,确定人们对 Lisp 重新燃起热情的具体时间并不可能;但这多半是保罗·格雷厄姆发表他那几篇声称 Lisp 是首选入门语言的短文之后的事了。保罗·格雷厄姆是 Y-Combinator 的联合创始人和《Hacker News》的创始者,他这几篇短文有很大的影响力。例如,在短文《 胜于平庸 Beating the Averages 》中,他声称 Lisp 宏使 Lisp 比其它语言更强。他说,因为他在自己创办的公司 Viaweb 中使用 Lisp,他得以比竞争对手更快地推出新功能。至少,一部分程序员被说服了。然而,庞大的主流程序员群体并未换用 Lisp。

实际上出现的情况是,Lisp 并未流行,但越来越多 Lisp 式的特性被加入到广受欢迎的语言中。Python 有了列表推导式。C# 有了 Linq。Ruby……嗯,Ruby 是 Lisp 的一种。就如格雷厄姆之前在 2001 年提到的那样,“在一系列常用语言中所体现出的‘默认语言’正越发朝着 Lisp 的方向演化” 23 。尽管其它语言变得越来越像 Lisp,Lisp 本身仍然保留了其作为“很少人了解但是大家都该学的神秘语言”的特殊声望。在 1980 年,Lisp 的诞生二十周年纪念日上,麦卡锡写道,Lisp 之所以能够存活这么久,是因为它具备“编程语言领域中的某种近似局部最优” 24 。这句话并未充分地表明 Lisp 的真正影响力。Lisp 能够存活超过半个世纪之久,并非因为程序员们一年年地勉强承认它就是最好的编程工具;事实上,即使绝大多数程序员根本不用它,它还是存活了下来。多亏了它的起源和它的人工智能研究用途,说不定还要多亏 SICP 的遗产,Lisp 一直都那么让人着迷。在我们能够想象上帝用其它新的编程语言创造世界之前,Lisp 都不会走下神坛。


via: https://twobithistory.org/2018/10/14/lisp.html

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

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


  1. John McCarthy, “History of Lisp”, 14, Stanford University, February 12, 1979, accessed October 14, 2018, http://jmc.stanford.edu/articles/lisp/lisp.pdf
  2. Paul Graham, “The Roots of Lisp”, 1, January 18, 2002, accessed October 14, 2018, http://languagelog.ldc.upenn.edu/myl/llog/jmc.pdf.
  3. Martin Childs, “John McCarthy: Computer scientist known as the father of AI”, The Independent, November 1, 2011, accessed on October 14, 2018, https://www.independent.co.uk/news/obituaries/john-mccarthy-computer-scientist-known-as-the-father-of-ai-6255307.html.
  4. Lisp Bulletin History. http://www.artinfo-musinfo.org/scans/lb/lb3f.pdf
  5. Allen Newell and Herbert Simon, “Current Developments in Complex Information Processing,” 19, May 1, 1956, accessed on October 14, 2018, http://bitsavers.org/pdf/rand/ipl/P-850_Current_Developments_In_Complex_Information_Processing_May56.pdf.
  6. ibid.
  7. Herbert Stoyan, “Lisp History”, 43, Lisp Bulletin #3, December 1979, accessed on October 14, 2018, http://www.artinfo-musinfo.org/scans/lb/lb3f.pdf
  8. McCarthy, “History of Lisp”, 5.
  9. ibid.
  10. McCarthy “History of Lisp”, 6.
  11. Stoyan, “Lisp History”, 45
  12. McCarthy, “History of Lisp”, 8.
  13. McCarthy, “History of Lisp”, 2.
  14. McCarthy, “History of Lisp”, 8.
  15. Graham, “The Roots of Lisp”, 11.
  16. Guy Steele and Richard Gabriel, “The Evolution of Lisp”, 22, History of Programming Languages 2, 1993, accessed on October 14, 2018, http://www.dreamsongs.com/Files/HOPL2-Uncut.pdf.[↩](#fnref16)[↩sup1/sup](#fnref16:1)
  17. Carl Helmers, “Editorial”, Byte Magazine, 154, August 1979, accessed on October 14, 2018, https://archive.org/details/byte-magazine-1979-08/page/n153.
  18. Patrick Winston, “The Lisp Revolution”, 209, April 1985, accessed on October 14, 2018, https://archive.org/details/byte-magazine-1985-04/page/n207.
  19. Harold Abelson, Gerald Jay. Sussman, and Julie Sussman, Structure and Interpretation of Computer Programs (Cambridge, Mass: MIT Press, 2010), xiii.
  20. Abelson, xxiii.
  21. Abelson, 428.
  22. Helmers, 7.
  23. Paul Graham, “What Made Lisp Different”, December 2001, accessed on October 14, 2018, http://www.paulgraham.com/diff.html.
  24. John McCarthy, “Lisp—Notes on its past and future”, 3, Stanford University, 1980, accessed on October 14, 2018, http://jmc.stanford.edu/articles/lisp20th/lisp20th.pdf.

Altair 8800 是 1975 年发布的自建家用电脑套件。Altair 基本上是第一台个人电脑(PC),虽然 PC 这个名词好几年前就出现了。对 Dell、HP 或者 Macbook 而言它是亚当(或者夏娃)。

有些人认为为 Z80(与 Altair 的 Intel 8080 密切相关的处理器)编写仿真器真是太棒了,并认为它需要一个模拟 Altair 的控制面板。所以如果你想知道 1975 年使用电脑是什么感觉,你可以在你的 Macbook 上运行 Altair:

Altair 8800

安装它

你可以从这里的 FTP 服务器下载 Z80 包。你要查找最新的 Z80 包版本,例如 z80pack-1.26.tgz

首先解压文件:

$ tar -xvf z80pack-1.26.tgz

进入解压目录:

$ cd z80pack-1.26

控制面板模拟基于名为 frontpanel 的库。你必须先编译该库。如果你进入 frontpanel 目录,你会发现 README 文件列出了这个库自己的依赖项。你在这里的体会几乎肯定会与我的不同,但也许我的痛苦可以作为例子。我安装了依赖项,但是是通过 Homebrew 安装的。为了让库能够编译,我必须确保在 Makefile.osx 中将 /usr/local/include添加到 Clang 的 include 路径中。

如果你觉得依赖没有问题,那么你应该就能编译这个库(我们现在位于 z80pack-1.26/frontpanel):

$ make -f Makefile.osx ...
$ make -f Makefile.osx clean

你应该会得到 libfrontpanel.so。我把它拷贝到 libfrontpanel.so

Altair 模拟器位于 z80pack-1.26/altairsim 下。你现在需要编译模拟器本身。进入 z80pack-1.26/altairsim/srcsim 并再次运行 make

$ make -f Makefile.osx ...
$ make -f Makefile.osx clean

该过程将在 z80pack-1.26/altairsim 中创建一个名为 altairsim 的可执行文件。运行该可执行文件,你应该会看到标志性的 Altair 控制面板!

如果你想要探究,请阅读原始的 Altair 手册

如果你喜欢这篇文章,我们每两周更新一次!在 Twitter 上关注 [@TwoBitHistory]​​6 或订阅 RSS 源了解什么时候有新文章。


via: https://twobithistory.org/2017/12/02/simulating-the-altair.html

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

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