第 109 期·中文精读

布莱恩·克尼汉:UNIX、C、AWK、AMPL 与 Go 编程

Brian Kernighan: UNIX, C, AWK, AMPL, and Go Programming

亲历者讲述 UNIX 与 C 语言的诞生,以及 AWK、AMPL 和 Go 背后的设计思想与工程文化。

官方音频00:00 / --:--
中文精读由 AI 基于原始访谈改写,非官方翻译,可能存在偏差或错误;表述以原音频与官方文稿为准。说明

1969 年,Ken Thompson 的妻子带着一岁的儿子去加州探亲,为期三周。他独自留在新泽西,用这三周写下了一个操作系统。

五十年后,世界上绝大多数计算机运行的都是它的后代。

这个系统叫 Unix。

布莱恩·克尼汉(Brian Kernighan)就在现场。他在贝尔实验室待了很多年,和 Ken Thompson、Dennis Ritchie 是同事,与 Ritchie 合著了《C 程序设计语言》,还共同创造了文本处理语言 AWK 和代数建模语言 AMPL。Lex Fridman 在节目开头说他是"我在这个播客里聊过的最谦逊、最友善的人之一"。而克尼汉整场对话反复说的一句话是:我不行,那主要是别人的功劳。

这种谦逊是真的。可他参与创造的东西,构成了今天几乎所有软件开发的地基。他自己在节目里开玩笑说,AWK 现在还有数百万人在用,所以"你的愚蠢错误也摆在他们面前让人看"。这句话其实才是这篇文章的入口:一个站在技术史关键节点上的人,怎么看待自己参与缔造的世界。

三周,一个操作系统

要理解 Unix 为什么会出现,得先回到没有它的年代。

1950 年代到 1960 年代初,大多数计算在物理上巨大的机器上完成,体积大,按今天的标准却一点都不快,被安放在专门的机房里。你写程序用打孔卡,把一叠卡片写好,交给柜台,柜台转给操作员,过一阵子回来的答复往往是"你这里写错了",然后你回去改,重来。整个循环非常慢。

分时系统的想法是:还是那台机器,但用类似电动打字机的终端接上去,让每个人轮流用一小片时间。你敲一个键,操作系统醒来,记下这个字符,然后回去处理别人的事。它绕着所有人转圈,给每个人一小块时间,制造出一种"这台机器基本是我一个人的"错觉。

这种错觉是好东西。它比打孔卡模式好太多,而且它催生了整个交互式计算的传统。

第一个比较像样的分时系统叫 CTSS,兼容分时系统。1966 年夏天,克尼汉作为实习生用过它,主持者是 Fernando Corbató,一位刚在这年去世的图灵奖得主。CTSS 跑在一台 IBM 7094 上,内存被扩到常规的两倍:两组 32K 字,每字 36 位,换算下来一次大概 150KB 乘二。今天这点内存在噪声里都找不到,当年却是一笔巨款。

CTSS 之后是 Multics。名字是 Multiplexed Information and Computing Service,多重信息与计算服务。它的野心是做一个"信息公用事业",可以让计算像墙上的插座一样即插即用。你听这个概念会觉得很熟,因为这基本就是今天云计算的描述,只是早了五十年。

Multics 由 MIT、贝尔实验室和通用电气三家一起做,通用电气提供硬件。麻烦在于它太贵,承诺的东西交付得太晚。1969 年初,贝尔实验室决定退出。

这一退,留下了一批已经尝过好环境滋味、现在却无环境可用的人。他们开始做纸面设计,想一个新的操作系统该长什么样。同时 Ken Thompson 找到一台使用不多的 PDP-7,开始拿它做文件系统的实验,琢磨信息该怎么高效地存到计算机里。然后他妻子去了加州,他坐下来,把它写成了一套操作系统。

克尼汉对这段有个明确的纠正。很多人以为他也是 Unix 的作者之一,他本人说:"我和它没有任何关系,我从没写过操作系统代码。"他说得很清楚,真正的作者是 Ken Thompson,加上 Dennis Ritchie 和周围一批人的巨大贡献。

"Ken Thompson 是我见过的程序员里的一种奇异点。没有对你不敬的意思,也没有对我自己,但他现在已经在好几个量级之外了。"(Brian Kernighan)

三周写一个操作系统,听起来像神话。这里面确实有运气成分,有那种"某个人在某个时间点恰好出现"的时机问题。但克尼汉也提醒,那时候的系统按今天的标准并不大。早期的 Unix 就是一段代码,让你能运行进程,能存文件,能加载可执行代码到内存,交给它控制权,跑完再收回来。再加上一个汇编器、一个文本编辑器、一套文件系统,核心就齐了。这些在 1973 到 1974 年之前,全是用 PDP-7 汇编语言写的。

用汇编写复杂系统难到什么程度?克尼汉说,在这种语言里你犯了错没人会告诉你,没有辅助轮。你只能靠某种看全局的能力,把东西一点点拼起来。他自己已经很久没写过汇编了,但那种"没有训练轮"的感觉他记得很清楚。

贝尔实验室:传奇是怎么被养出来的

贝尔实验室当时在新泽西有好几处,主址在 Murray Hill,跨在两个小镇的边界上,从纽约市往西大约十五到二十英里。那里有三四千人,很多是博士,主要做物理科学,化学、物理、材料,数学也很强,同时对计算的兴趣越来越大,因为人们发现可以用计算机去建模、去替代一部分实验室工作。

它隶属于 AT&T。AT&T 提供的是全美的电话服务,贝尔实验室的目标就是让这个服务不断变好。这个目标听起来平淡,但它决定了一切:为了改进电话服务,你需要做晶体管,需要做光纤,需要做微波系统,需要研究材料、器件和数学。指标看起来是服务,实际落到研究上,就是有大量基础问题可以啃。

克尼汉强调那个环境非常开放、非常合作,友好到有点不像大公司。你有问题就去找人,对方可能就帮你解决。人们会干一些奇怪的事,时不时地戳一戳官僚体系,带点反叛的意味。

他特别提到物理上的接近:贝尔实验室本质上是一栋巨大的楼,做相关工作的人挤在两三个楼区里,还有一个五十英尺乘五十英尺左右的公共空间,放着咖啡机,人们在那里闲逛。反馈循环非常短。你早上有个想法,晚上或者第二天就有一个能跑的东西,别人会来试,会说"这很棒,但你这里搞砸了"。

这是不是独一无二?克尼汉立刻用了"幸存者偏差"这个词来打自己。他说,如果这事没发生在贝尔实验室,别的地方也在做同样有意思的工作。施乐帕克研究中心(Xerox PARC)就是最明显的例子,贡献了多得惊人的东西,很多我们今天习以为常的技术都出自那里。只不过帕克的母公司可能没那么幸运,没有把这些成果好好变现。

回到编程本身。克尼汉说,早年编程的乐趣之一是有大量的低垂果实,前人没做过,环境又是新的,工具好用,系统响应快,同事极强。他把这种体验总结成一件事:编程本身是解谜,是好玩,但真正让好玩变得更好的,是你做的东西有人用。哪怕他们在抱怨它坏了,他们用了,这件事本身就是奖励。

Unix 的设计哲学:为程序员服务

Unix 的根本目标,按克尼汉的说法,是提供一个让人容易写程序、写程序高产的环境。它的定位就是为程序员服务的环境,别的用途都是顺带。

这一点值得细读。它被大量用于文字处理,但它不是作为文字处理系统设计的;被大量用于实验室控制,但它不是为此设计的;被大量用作大型系统的前端,也不是为此设计的。它就是让写程序变得容易。相应地,写出来的程序也常常是给别的程序员用的,于是形成了一个正循环:环境越好写程序,就有人写越多有用的程序,越多有用的程序又让环境更有价值。

Dennis Ritchie 在书末有个观察,说从他的角度看,真正的目标其实是创造一个社群,一群人可以在这个系统上作为程序员一起工作。这个说法比任何技术描述都更贴近 Unix 的本质。

Unix 的效率和健壮性从哪里来?克尼汉给的理由很朴素:它从极其简陋的硬件上开始,处理器速度和内存都非常有限,这逼着你不能乱来。没有资源去堆复杂的东西,于是你被迫去找一个机制通吃许多场景,而不是写一堆特殊分支。

文件系统是最好的例子。Unix 的文件接口极其直接,这让围绕它写代码非常高效。然后有人发现,这个接口不只能管磁盘文件,还能管设备:对设备读写和对文件读写是一回事。

这个泛化思路后来被推得更远。Plan 9 操作系统在 1980 年代末出现,尝试把更多资源都塞进同一个接口,进程、网络、各种文件系统,全部共享那套统一的抽象。每种资源用同一把钥匙去开,用的人就不用每次都重新想一遍。

这里藏着一条可以带走的规律:找到正确的模型,能让很多事同时变简单。省下来的认知成本,会转化成人人都能多做一些有用的事。

一个"开源"的误会

Lex 问到一个很常见的印象:Unix 后来成了开源系统,因为 AT&T 受各种约束,没法自己做计算机生意。

克尼汉直接纠正了这个说法。Unix 从来不是开源的,它是明确的专有许可(proprietary)。只是它以源代码形式,长期免费授权给大学。正是这一点,让一代代学生和老师看着 Unix 长大,也让社区里积累起足够的专业能力,人们才有可能各自分头去搭一个"长得像 Unix"的东西。

伯克利版的 Unix 从这个许可代码起步,逐渐加入自己的贡献,尤其来自 Bill Joy 等人的代码。加得足够多之后,它最终能完全摆脱 AT&T 的代码。1980 年代末到 1990 年代初,围绕这件事打了大量法律官司。

开源运动这个说法,大致可以从 Richard Stallman 在 1980 年代末的想法算起。到 1991 年 Linus Torvalds 决定写一个类 Unix 操作系统时,社区里已经有足够多的知识存量。他能看见目标,因为 Unix 的系统调用接口、工具集早就在那里摆着。于是他搭出的是一个到今天说起"Unix",你脑子里其实想的是 Linux 的东西。

所以那句"技术上不开放,技术上属于 AT&T,但在实践中相当开放",是对这件事最准确的概括。

AWK 与 grep:一行代码的威力

AWK 是克尼汉和 Al Aho、Peter Weinberger 在 1970 年代末做的,名字来自三个人姓氏的首字母。它的定位是让"又快又脏"的任务变得容易:数个数、从文本里挑出有意思的信息、换个方式重排、总结一下、对每一行跑一条命令。

它到今天仍然被广泛使用,已经四十多年了。克尼汉说,他们当初没意识到,这个模型其实对很多数据处理任务都很好用,所以它能活这么久。他半开玩笑地给了个建议:如果按"投入产出比"来算,学 AWK 大概是学一门编程语言里性价比最高的。它撑不起大项目,但在那些"我就想看看这里面所有的某某东西"的小事上,表现相当好。

AWK 之所以顺手,很大程度上在于它的默认行为设计得好。它自动逐文件、逐行读,每一行上你用一组模式和动作来写程序:如果某个模式匹配,就执行对应的动作。相当于一个四层嵌套循环,全部自动完成,你只要写模式和动作。它还自动把行切成字段,自动收集信息,比如这是第几行、这行有几个字段。

结果是,在别的语言里要五到二十行的东西,在 AWK 里一两行就够。因为只有一两行,你可以直接在命令行里做,不用另开一个环境。

grep 更简单。它搜输入里符合某个正则表达式的行,模式匹配就打印。它和 AWK 是同一种范式,只是模式只有一个、动作也固定成"打印"。克尼汉说自己现在可能用 grep 比用任何别的东西都多,因为太方便、太自然。

为什么 Windows 一直没有原生的 grep?克尼汉的猜测是历史包袱加设计取向。MS-DOS 是个相当糟糕的操作系统,1990 年代某个时候 Windows 变成图形系统,微软把能量都投在这个图形界面上。那是一种面向非程序员的计算模型:点、点、点,试菜单。对只想把事做完的人,这个模型其实挺好用,反而是把命令行教给非程序员常常是一场艰难的 uphill battle。

"从微软的角度,他们做的大概是对的。"克尼汉说。长期以来的替代方案是 Cygwin 这类东西,把 Unix 和 Linux 上你喜欢的工具全部搬过去,让它们在 Windows 上正常工作,这套东西已经存在至少二十年。今天还有 WSL,能让你在 Windows 里写 Bash 脚本。Lex 说自己用过之后"生活都变了"。

编辑器考古:从纸张到 vi

克尼汉平时用什么编辑器?Sam,Rob Pike 很久以前在贝尔实验室写的。它的年代晚于 vi 和 Emacs,源头可以追溯到最初的 Unix 编辑器 ed。

这段历史挺有意思,因为它解释了今天的编辑器为什么长成这样。最早的分时系统上有编辑器,完全基于命令。那时候没有鼠标,甚至没有光标,因为终端是把字打在纸上的。Ken Thompson 写过 QED,功能非常强。Unix 出现后,他把 QED 大幅削减,变成了 ed:一个行编辑器。你可以加载文件,然后按行操作,打印某几行,加文本、删文本、改文本,或者用替换命令改行内的内容。它的界面完全基于命令行,输出打在真纸上。你改一行,就得把那一行重新打印出来,用掉另一行纸,才能看到改成了什么。

CRT 显示器出现以后,才有可能用光标控制,才能在屏幕上移动位置而不用重新打印一切。那个年代出现了好几个编辑器,克尼汉最熟悉、至今偶尔还在用的是 Bill Joy 写的 vi,大概出自 1970 年代末,充分利用了光标控制。Emacs 大约同时,但克尼汉说自己从来没把 Emacs 内化过。

所以他现在的主力是 Sam,有时用 vi。Sam 在大多数系统上都能弄到,通常要从 Plan 9 发行版里自己下载。

从汇编到 JavaScript:语言的上升史

克尼汉给的编程语言简史,从打孔卡和面板开关开始。最早人们用零和一对机器编程,后来有了汇编器,把 add 这样的助记符翻译成对应的位模式,顺便处理"这个东西在内存哪里"的琐碎工作。

他引用了一个刚过世的例子:Tony Brooker 在曼彻斯特做的 Auto code。它的前身是 Alan Turing 写的一套语言,你得把指令用零和一写进去,而且要倒着写,因为硬件就那么工作的。

汇编时代的特点是每台机器一套汇编语言,IBM 有它的,曼彻斯特有它的,各不相同。汇编指令和机器的指令集基本一一对应。你必须非常了解这台机器才能写程序,而且换一台机器就得从头开始。

1950 年代末,人们意识到可以把这套游戏再升一级,造出更接近人类思考方式的语言。那几年出现了 FORTRAN,来自 IBM,是 Formula Translation,让科学和工程计算变容易;出现了 COBOL,Common Business Oriented Language,Grace Hopper 等人参与,面向商业任务;出现了 ALGOL,主要用来描述算法;Basic 稍微晚一点。

这次升维带来两件事。第一,程序只写一次。不同的编译器可以为不同硬件编译,程序不再绑死在一种机器上。第二,写代码这件事被民主化了。一个物理学家或工程师,自己就能把程序写出来,不必去请专业程序员。写出来的质量可能不如专业程序员的,但已经够用。

1970 年代出现系统编程语言,C 是唯一的幸存者。这类语言要能干系统程序的事,比如文本编辑器、汇编器、编译器,甚至操作系统本身,所以它得能管内存、能访问进程,和机器打交道的程度更高,但方式更可控。之后是面向对象,C 写到一定规模就撑不住了,太多碎片没法追踪,C++ 从那条传统里长出来。再然后就是语言的爆炸,Java、JavaScript,一路到今天。

C 的甜蜜点

为什么 C 能活下来,影响这么深?克尼汉的说法是它卡在了一个甜蜜点上:表达能力足够让你自然地重写东西,同时效率也够高。在高效率尤其重要的年代,这两点很关键。把时间倒回五十年前,那就是四五代摩尔定律之前,机器的能力差得远。

还有一个因素是环境。C 和 Unix 绑在一起,写出来的程序可以在所有跑 Unix 的机器上用,而所有这些机器又都是用 C 写的。操作系统本身可移植,工具也可移植,它们互相喂养,形成正循环。

《C 程序设计语言》这本书 1977 年开始写。当时的时机很好:Unix 正在扩散,从几十个系统涨到几百个,C 也在和 Unix 无关的机器上出现。市场上没有别的 C 书,贝尔实验室是唯一权威来源。Dennis Ritchie 本人就是权威,因为他设计了这门语言,还写过一份极其出色的参考手册。克尼汉说他"拧着 Dennis 的胳膊",直到他同意一起写书。

书里最有名的遗产大概是 Hello World。克尼汉对"好例子"的看法很具体:一个好的例子会告诉你如何做一件事,而且它代表的东西,你未必想原样照做,但你多半想做同一类的事。他花了很多时间找那种能连成一串、能告诉读者"你需要知道什么、以及什么时候会需要"的例子。

他对很多编程书的抱怨是:不给现实的、你可能会真的想做的例子,只给纯语法。"这里怎么把三个数加起来,得了吧,这个我自己能想出来。告诉我这三个数怎么进到计算机里,做完有用的事以后,怎么又整整齐齐地输出回来。"

这句话背后是一种很朴素的教学观:人是从能用的东西里学的,不是从语法清单里学的。

Go:为 21 世纪准备的 C

Go 有两个源头。一半是贝尔实验室的传统,三位创造者里的 Ken Thompson 和 Rob Pike 都是那里的人。另一半来自欧洲学派,经由 Robert Griesemer 传过来,他算是第二代下传的学生。这个组合挺有意思。

在克尼汉看来,Go 抓住了 C 里好的那部分:表面上看非常像 C,有人干脆把它叫做"21 世纪的 C"。同时它又引入了不错的数据结构能力。他自己不是 Go 专家,虽然合著了那本 Go 的书,但他说那本书 90% 的工作是另一位作者、真正的 Go 专家 Alan Donovan 做的。

他认为 Go 特别有用的地方是并发模型。它基于 Tony Hoare 四十多年前提出的"通信顺序进程"(CSP)。goroutine 在他看来是描述并行计算非常自然的方式,写起来容易,通常能正常工作,效率也不错。

顺带问一句,早期 Unix 的人有没有预见到线程和并行计算?克尼汉猜没有。也许有人看到了,但很长时间里,这不是一件你必须去处理的事。处理器越来越快,然后因为功耗和散热这类问题,不再变快,于是转向往一颗芯片上放更多处理器。并行的东西就是在这种背景下变得重要的。

用 20 种语言写同一个程序

克尼汉有个习惯:课堂上他会拿一个小例子,尽可能用不同的语言写一遍。现在手上有二十个版本。

这个例子本身很无聊:读入长度不一的文本行,把每行重新折成不超过 60 个字符。在一门语言里大概十五行,别的地方也差不多。他关心的三件事是:代码多长,跑多快,学起来多痛苦。

这当然是极窄的样本,但他有他的道理。"一门语言最难的一步,大概就是第一步。"他用这个小程序去迈那第一步,然后看自己经历了什么。

结果差异很大。在 Lua 里,他下载了 Lua 环境,一个小时就做出来了,之前一辈子没写过 Lua。Scala 同样简单,他试了,却花了几个星期,而且跑得像乌龟。FORTRAN 90 很痛苦,但能跑。Rust 花了几天,内存管理的模型对他有点陌生。他当时遇到的最大问题是找不到一致可用的文档,感觉手册里的一切都在快速变化,看起来能跑的示例,在他的语言版本上跑不起来。这件事发生在几年前,他说现在应该稳定多了。

他的总结落到实处:你得有一件想做的事,还得有足够可支配的时间,两件都满足,才值得去学一门新语言。光为了学而学,这事永远不会发生。

那 JavaScript 呢?当年它被公认为最丑的语言之一,学术界提起它会嘲笑你,说它不配被学术研究。克尼汉认为相当一部分已经变了:语言本身在演进,编译技术更是好得不可思议,所以前后端都已经是可行的方案。他自己写过一些,也玩过一些翻译器,但不是专家,这里变化太多,他也跟不上。

库的甜蜜负担

Lex 问了一个很敏锐的问题:今天写 Python 或 JavaScript,很大一部分体验其实是在用别人写好的库,这和当年在 Unix 和 C 上从零开始搭东西,感觉完全不同。

克尼汉承认这是关键差异。旧时代编程之所以好玩,原因之一是你真的在全部自己搭,要处理的库相当少,最多加一个标准库。今天不是这样。

他的描述很具体:这几天的机器学习的尝试里,有东西不工作,你就 pip install 某个包,几百兆的不知道什么东西就下来了,你完全不知道它是什么。运气好它就能用,运气不好你毫无办法,而且你根本没法从这一千个包里找出是哪个出的问题。

他觉得 JavaScript 的 NPM 环境更糟,纪律和管控都更少。这类问题的后果不只是"你不懂它怎么工作",还包括安全问题、健壮性问题。"所以,你不想用 JavaScript 去跑一个核电站,大概。"他说得像是玩笑,但半是玩笑的东西往往最真实。

AMPL:把数学翻译给求解器

AMPL 是克尼汉参与设计的代数建模语言,和 Bob Fourer、Dave Gay 一起做的。这件事在他自己的说法里,主要功劳也归两位搭档:Bob 是西北大学工业工程与管理科学系的教授,Dave 是贝尔实验室的数值分析和优化专家。

线性规划是什么?你有一堆决策变量,它们必须以某种方式组合,满足一组约束,然后你要找到一个取值,让某个目标函数最大或最小。教科书里通常写成:一个大矩阵 A,乘以向量 x,小于等于向量 b,再加一个目标函数,是若干 x 的线性组合。

问题在于真实世界里的 A 非常庞大、非常稀疏、结构非常复杂,各个组件分散在系数里,人几乎没法直接看懂。你在黑板上写的是数学:"这一堆的和大于那一堆的和",而你真正需要的是把黑板上的数学直接写成约束,再交给求解器。

Bob 长期以来对建模语言有兴趣。当时已经有一个叫 GAMS 的通用代数建模系统,但语法像 FORTRAN,很笨重。1984 年,Bob 到贝尔实验室做休假年,办公室就在克尼汉对面,地理上非常近,于是两个人加上 Dave 一起开始谈这件事。

那种语言应该写成什么样子?无非是像黑板上的代数式,用文本写出来。克尼汉写了 AMPL 的第一版,这也是他人生第一个 C++ 程序,大概三千行,不算大,但它证明了这件事可行:人可以自然地写模型,机器能把它转换成求解器能处理的东西。

关键设计是模型和数据分开。同一个模型可以用在完全不同的数据上,就像很多程序那样。求解器负责所有硬活,AMPL 把求解结果转换回你原来的形式,告诉你每样东西该买多少、做多少、运多少。

Lex 提到他自己用 AMPL 时,会把模型发给确定没编程背景的同事看,对方也能大致理解那个优化问题是什么。一个语言能让人读懂问题本身,这件事比它跑得多快重要。

P 等于 NP 吗?

Lex 逼克尼汉下注:你觉得 P 等于 NP 吗?

答案是"不"。他说自己没有直觉,但读到的很多同行都这么下注,这是流行看法。他还转述了一个关于 Jeff Dean 的段子,问他在什么条件下 P 会等于 NP,回答大概是"要么 P=0 要么 P=1 之类",克尼汉说自己记不清了,反正那人比他聪明太多。

克尼汉博士论文写的是关于大 O 符号的东西,1968 年,早于计算复杂性理论的成形。他当时做的是图划分:给定一个有节点有边的图,边有权重,你要把节点分成两堆、大小相等,让跨越两堆的边数尽可能少。这个问题很难。

他和 Shen Lin 在贝尔实验室一起做,始终没能找到任何保证给出正确答案的算法。他们做出了效果相当好的启发式方法,他也为论文挑出了一些特殊情形,但就是难。那个时间点,Steve Cook 正在证明有一类问题看起来真的很难,图划分是其中之一。

这里有个细节值得琢磨。Lex 拿最大割作对比:最大割和图划分是同一个问题,只是不限制两边节点的数量,而一旦加上这个约束,它就变难了。约束的存在与否,把一个容易问题变成了难问题。

AI 的夏天、冬天和新夏天

1964 年,克尼汉在多伦多读本科,毕业论文是一篇关于人工智能的文献综述。那正好是 AI 的夏天,人们以为计算机什么都能干:机器翻译、下棋、证明几何定理,样样都能。他读了《计算机与思维》那本论文集,某种程度也算喝了一点 Kool-Aid,人非常乐观。

后来大家都知道了,当时以为几年就能实现的东西,花了几十年。下棋和下围棋现在确实比人强了,机器翻译也好了很多,但这背后是五六十年的进步、硬件的演进,以及大量可以用来学习的数据,再加上互联网和开源让开发者协作的基础设施。

从这里能抽出什么教训?克尼汉引用了科幻作家 Arthur Clarke 的一句话:一位德高望重的老人说某件事可能,他多半是对的;说某件事不可能,他几乎肯定是错的。但没人知道时间尺度。

谈到今天这波机器学习和神经网络,他说自己离专家非常远,大部分是从学生那里学的,学生们大概会对他学得这么少感到失望。但他认为潜力很大,游戏领域已经看到效果。同时他有一个实实在在的担心:机器学习从我们给它的数据里学,数据有问题,学到的东西就有问题。最明显的例子就是偏见,如果数据因为历史的对待方式而包含"某些人不如另一些人"这种明摆着错误的东西,机器学习就会把它传播出去。

而这个担心还有光明的一面。机器学习做的事,是把数据里的偏见暴露出来,像一面镜子照向我们自己的社会,从而帮我们去修正它。克尼汉说,如果它真的是这样运作的,那会很棒。但他不知道它到底是不是这样运作,或者反过来说,机器学习的机制会不会反而强化和放大过去就错的东西。这件事必须严肃对待,不能含糊过去。

至于"造出人类水平的智能需要什么",他的回答非常干脆:我完全不知道。

摩尔定律撞上原子

指数增长能永远持续吗?克尼汉给了个俏皮的回答:没有哪个指数能永远持续,总会在某处耗尽。但如果它持续得足够久,那可能就是我们需要的全部了,反正跟我们没关系了。

他注意到摩尔定律已经在变。处理器不再变快,于是人们把"同样面积塞更多东西"的能力用在横向增长上:一颗芯片上放更多处理器、更多内存。这会撞到限制吗?大概会,因为总有一天你会逼近单个原子,得另想办法绕过去。会不会找到绕过去的办法?不知道。他说这话时还补了一句:"我刚说过,如果我说不会,我肯定会错,所以也许我们会找到。"

那编程语言会怎么变?他的判断是,会延续一些领域已经出现的趋势:更多编程由程序完成,更少由人完成;更多是声明式,过程式的成分越来越少。你只需要说我想要这件事发生,由别的东西去搞清楚怎么做。今天这在许多垂直领域已经是常态,领域特定语言的天下。可以想象这个范围会变宽,你不需要写那么多细节,某个软件集合会弄清怎么做你想做的事。

一句话概括:抽象层次会继续升高。高到某一天,也许就到了能用自然语言描述意图的地方。

计算机如何改变了人与人

克尼汉在普林斯顿教一门叫"计算机与我们世界"的课,面向非技术学生,典型的是历史系或英语系的人。他要讲计算机如何工作、编程是什么、计算机之间如何通信、通信的时候在干什么。

这门课里有个设计很有意思:他会带学生看一台玩具机器,指令集很小,让学生写几行汇编,理解最底层是什么样,计算机是如何用一小撮非常简单的指令(加减、跳转之类)把事做完的。然后往上走到 FORTRAN、C 这样的高层语言,看抽象层次如何让人更容易写代码、更不依赖具体架构。最后再讲他们天天在用的那些程序:手机上的操作系统、下载的 App,全都是我们刚说过的这类东西,只是规模巨大。

他承认这是要求学生从五行汇编跳到一个手机浏览器,概念跨度巨大。但本质上,它们是一回事。

那么计算机到底怎么改变了世界?克尼汉抓了一个角度:人与人之间的互动方式。

他小时候,打一个电话到某个地方是件大事,因为要花不少钱,那是 1960 年代。今天人们不打电话了,发短信。人们觉得和世界任何角落的朋友、家人定期通信、开视频会议,是理所当然的。

这对人是好是坏?他给了个两面兼有的答案。他自己和加拿大的兄弟姐妹发电邮,比二十年前打电话频繁得多,大概每两三天就会收到或发出一封,这在某种意义上把他们拉得更近了,是好事。但他看着校园里的孩子,大多低着头盯着手机走,他得躲着走。社会学研究里有人指出,人们实际上反而不如以前亲近了。他不知道这是不是真的,但他能看见潜在的下行面。

更宏观的一层,他看到的是双面性。计算机给了人们改善生活的可能,这是好的。同时短期内也能看到大量坏的一面:人变得更部落化、更狭隘,而且数量庞大。人们用计算机去误导、歪曲,或者干脆对正在发生的事撒谎,这件事正在影响各地的政治。

Lex 补了一句:现在每个人都有了发声的渠道,被压迫的人有了声音,这是强大的事。但每个人都有了声音,随之而来的混乱,看着既迷人又可怕。

大多数人不该学编程,但该懂编程

回到那个最普通的问题:所有人都该学编程吗?

克尼汉的回答很克制,也很明确:大概不该。但每个人都应该至少理解编程是什么。这样当有人对你说"我是程序员",你脑子里有个大致的概念;当有人说"这是由计算机运行的程序决定的",你对那意味着什么有一个模糊但准确的直觉。

他的这门课就是干这个的,讲计算机怎么工作,讲程序怎么写,讲计算机之间怎么对话。他坦白说,这门课上极少有人后来成为严肃的程序员。但至少他们对这一整套东西有了更好的认识,而且不只是编程,还包括背后的技术和通信。

课程里学生要写一点程序,很少。他带他们看清机器在高层语言之下是怎么运作的。虽然严格说没有"最低层",你总能再往下走,但他说,他在那里画了一条线。

那些真正开心的时刻

Lex 最后问,如果能回到过去重活一个瞬间,那个让你真正快乐、或者深刻改变你的瞬间是哪一个?

克尼汉没有给出某个具体时刻。他给出的是一类时刻,和地点绑定:在贝尔实验室,你做出一件东西,它工作了,有人用了,说"这挺酷"。这种事在 1970 年代那个黄金时代发生得挺频繁,Unix 还年轻,低垂果实遍地都是,一群人在一起,你做点东西,他们就会来试。

AWK 就是这样一个例子。他做出的东西,全世界几百万人天天在用。他也因此把自己所有的愚蠢错误都摆在这些人眼前让人看。他说这事既好又可怕。

Lex 把这一点说得很漂亮:它让人脆弱,也美,因为它实实在在地影响了很多人。

克尼汉自己留在节目最后的那句话,只有七个字:"不要注释糟糕的代码。重写它。"

这句话可以当成他整场对话的一个总结。他不相信复杂的补救,不相信在坏东西上叠加解释,不相信绕弯子。他相信的是把机制找对、把模型找对,然后让一切自然地变简单。这既是他参与缔造 Unix 的方式,也是他看待编程、语言、工具、乃至技术与社会关系的方式。

如果你读到这里想做点什么,下面这些是从这场对话里能直接拿走的:

  • 花一个下午学会 AWK 和 grep。克尼汉说,按投入产出比算,它们大概是最值得学的工具。你不需要精通,会写一行"从一堆文本里挑出某个东西并数一数",就足够在日常里反复省时间。
  • 学一门新语言时,别从语法开始,从一件你想做的小事开始。读入若干行、重新折行、输出,这种十五行左右的程序,一小时能跑通的语言,往往在默认行为设计上做得好。
  • 写代码之前先问清楚"这个问题真正要想清楚的是什么"。克尼汉把这一步归为艺术,把"怎么做才做好"归为科学。多数人跳过前者,直接进入后者,然后在错的路上狂奔。
  • 接受"找到一个好模型"的价值。Unix 的文件接口之所以强,是因为一个机制通吃了很多场景。你手上的系统如果能找到一把这样的钥匙,后面会省下大量重复解释和重复实现。
  • 对库保持敬畏,别一 install 就当它没有。知道自己在依赖什么,尤其是在安全敏感的场合。几十兆你不认识的代码进到系统里,这件事值得停一下想想。
  • 把数据当成一面镜子。如果模型学到了奇怪的东西,先回去看数据里有什么,而不是先调模型。偏见在数据里,它就会在学习结果里。
  • 别给糟糕的代码写注释,重写它。这一条适用面比编程宽得多。任何靠解释维持的东西,都值得先问一句,能不能换一个模型让它不需要解释。

本集资源与出处