星际旅行的推进技术

Sep 17, 2010

Comments

1969 年,美国国家宇航局 (NASA)完成了人类历史上的一次创举:首次把人送上了月球。执行此任务的推进器,是由纳粹德国的著名火箭科学家,二战后加入美国国籍的冯布劳恩设计的“土星五号”火箭。

“土星五号”火箭是人类历史上最大,最高和最重的多级火箭,比已经是庞然大物的航天飞机要庞大许多。 “土星五号”之所以这么庞大,原因在于阿波罗计划需要将一个轨道舱,一个登月舱和能够让宇航员从月球轨道再返回地球轨道的燃料送到月球轨道。即使是使用液氢这样比冲 (Specific Impulse) 较大的推进剂,在发射期间,平均前进一英里,也要消耗掉两万五千磅的液氢推进剂。送上月球尚且需要如此费力,载人送上其他比月球还要远很多的行星的难度就更加难以想像了。

事实上,在火箭科学中,有一个著名的“火箭公式”,大致是说火箭在飞行过程中的速度变化,和火箭发射前后的重量的比例的对数成正比。如果想要足够快的行星际航行,火箭的飞行速度,要从发射时候的零到变到很大,才能逃逸地球的重力陷阱,进入太阳轨道;最后,又要减慢速度,才行进入其他行星轨道或者着陆。简单的数学推倒可以发现,这样大的速度变化,会导致火箭发射时候所携带的燃料和火箭的有效载荷成指数级别的关系。 不大精确的说,如果要送一个载人航天器和所需的设备,食物等在半个月之内到火星的话,我们要把波斯湾一天出产的石油全部装在火箭里做燃料才行,而这需要一个十几千米高的火箭。 所以,科学家一度认为, 仅靠喷气式引擎,人类不要说星际迷航了,在足够短的时间里飞到太阳系的其他行星都是个大问题。

其实这个问题早就有了解决的方法。 1918年苏联科学家夏格尔就提出,如果想要行星际旅行中航天器的速度加快,一个方法是让航天器掠过一个运动的行星。 因为行星本身也是运动的,航天器能够获得两倍行星速度的加速。 这就好比用一个运动的铁球去撞一个玻璃球,玻璃球能够反方向弹开,而且还能获得两倍的铁球的速度。 在这个巧妙方法的协助下,美国的水手10号探测卫星掠过金星被推送到了目的地水星,而著名的旅行家号,更是利用100多年一遇的行星排列的机会,一举掠过木星, 土星, 天王星和海王星。 现在,凡是 NASA 的行星探测器,没有一个不是通过掠过其他行星的方法获得加速到达目的地的。

如果对速度没有要求, 太阳系行星间旅行还有另一种方法,就是利用行星的合力。 如果一个人类卫星位于两个行星平面上的某些点的话(黑话讲叫三体问题的拉格朗日点), 这个卫星受到两个行星重力的合力的效果,能够让卫星处于一个相对两个行星静止的位置。因为行星本身是运动的,所以卫星完全以不消耗能量或消耗极低能量的方式在太阳系里面按照一定轨道运行。 只要精确的计算和利用这些轨道,卫星就能在太阳系里面以一种非常节能的形式从一个点滑到另一个点,当然需要的时间可能巨长无比。当然这不要紧,如果人类要建立火星或其他行星基地的话,我们可以极低的代价把大规模的物资在太阳系里运来运去。

喷气式引擎带来的第一代推进技术让人类终于可以进入近地轨道, 重力加速技术让太阳系中行星际旅行成为可能,可是如果人类需要星际间的航行,靠以上两种技术就完全不够了。 到底下一代的推进器技术是反重力引擎,还是 WarpDrive, 就等聪明的人类再发明吧。 但愿在有生之年能看到星际航行成为现实。


Dragon*Con 2010

Sep 7, 2010

Comments

这个 Labor Day 和老婆去亚特兰大参加 Dragon*Con 了。 这可真是一个科幻迷大聚会,拍了些照片贴在我的网络相册里了,有兴趣看看美国科幻迷大聚会的各位老师随意。

Dragon*Con 2010

Jolt 大奖和传统杂志的消亡

Aug 10, 2010

Comments

(本来想写 Jolt 大奖的八卦的,发现读书太少没法写,只能写成扯蛋文了)

在互联网,特别是 web 出现之前,信息的比特是通过其他网络传播的。在程序员之间,那时候大部分小程序都是直接贴在杂志上的,而大的程序则通过人之间互相拷贝来实现。

在 杂志上贴可以运行的代码和实现技巧对于我们现代人来说已经不是什么潮流的东西了,可对当年的程序员来说,每月一期的杂志简直就是无价的宝贝--上面充满了 其他天才写的几十行的小游戏和小工具,还有实现技巧。 在计算机科学中,CACM 可算是杂志上贴代码的巅峰。在 Communication of ACM 还是非常高深的期刊的时代(现在变成科普期刊了,以前大多是专业文章),因为人人都在这个杂志上贴代码,所以 ACM 决定对算法编号,这些编了号的算法日后就成了一个巨大的算法库,被称作 CACM Algorithms. 有兴趣的读者可以自己去看看这些千奇百怪的算法 ( http://calgo.acm.org/). 这套算法命名体系是曾经是如此的通用,覆盖又是如此的广,以至于现在仍然会有黑客直接说 algorithm 232 (CACM 232号算法是一个堆排序的实现) 而不是 heapsort 等等。

在美国,这样的为开发人员服务的杂志全盛时期并不长,尤其是 mailing list 和互联网普及之后,就在逐渐的消亡。web 出现之后,这些杂志很快就被全部打败了。 我们这里要八卦的 Jolt 大奖,就是由曾经这样的一个为开发人员服务的杂志发起的。这个杂志的名字叫做 Software Development。 可以想像,随着软件工程往大方向走,这个杂志除了放点技术评论和书评,基本上很难和互联网以及mailing list 抗衡。 软件工程复杂化一方面造成原来愿意给杂志写稿的人更加愿意写书,另一方面造成原来愿意读杂志的人,要不去上网获取快速知识,要不干脆捧起大部头。 所以,这样一个杂志,在互联网时代,就只能以技术和行业评论为利基了。 SD 杂志在可以说在这个利基上做的非常好,最有名的就是推出了 Jolt 大奖。

Jolt 是美国的一种能量饮料,据说喝了能活力百倍。Jolt 大奖就是奖励给那些给程序员提供活力,生产率的工具,产品,图书,理念,最佳实践等等。 因为 Jolt 从一开始就邀请专家组来提名评审,而非让普通人投票,所以有很浓重的奥斯卡色彩,这 Jolt 大奖也被成为软件产业的奥斯卡。 Jolt 大奖其实只是一个概称,常常一个 Jolt 奖, 三个 productivity 奖,基本可以理解成一个一等奖三个二等奖。 读者都知道软件行业里图书也好,工具也好,每年都层出不穷,自然 Jolt 奖是一个竞争激烈的奖。Jolt 奖从 1990 年开始颁发,到现在已经将近 20 年了。我整理了一份这20年 Jolt 奖的所有图书的单子,有心的读者可以看看,应该能看出这20年软件工程发展的脉络的。 Jolt 奖现在已经涵盖到 12 个分项(所以每个月提名一项),我觉得一个追求不落后时代的开发人员还是可以时不时看看 Jolt 奖的提名的那些技术和图书的。

Jolt 奖并没有能够拯救 SD 杂志被 Web 杀死的趋势。 在 2006 年左右的时候,SD 杂志把出版部门全部打包卖给了 DDJ, 另一个以贴 BASIC 代码起家的杂志,自己专心做 web 新闻站了。 DDJ 也没有撑过几年,于 2009 年,把出版部门全部出让给了 Information Week, 仅每月在杂志中间留这么10页左右的技术评论。 早在互联网杀死新闻周刊之前,互联网就把这些技术杂志全部杀死了。 好在 DDJ 这类杂志还能继续在网上运营,所以 Jolt 大奖始终还有评选。 只是互联网成了程序员的新 Jolt,这个 Jolt 是如此的无孔不入,以至于传统媒介都被杀得丢盔卸甲了。

本文尤其欢迎图老师留言讨论。


养儿防老

Aug 8, 2010

Comments

才给我爸妈打电话,他们十分钟前才把我弟弟送上来美国的飞机。我和他们打趣的说:“你们又送走一个儿子,这下两个全在美国了,你们身边没有儿孙满堂绕着转了”。其实我爸妈倒是很开明,他们身边的人对他们不把孩子留在身边以养儿防老表示关切的时候,他们总是说:留在身边影响小孩发展,不如全送出去。

从我个人来说,虽然基本上被美国价值观改造得以后绝对自己不养儿防老,对父母我还是要尽赡养的责任的。我觉得这个是道德和经济上都理智的选择。 道德上我就不扯大旗了,说说经济上。贵国 我的伟大的祖国 的人都知道,贵国 我的伟大祖国 特色的社会主义的一大特色就是普通人没有像样的社会医疗保险。我父母(特别是我妈),在辛苦工作交税一辈子之后,拿到的养老保险和退休补贴非常有限。人一老,收入是有限的,医疗上的支出却是摸不着底的。我小时候看到很多邻居老人因为一场大病搞得破产分家的都有,深切体会过这个扯蛋的体系对人性的撕扯。况且现在这个局势发展下去,就是地主家也没有余粮了,何况我们普通人。所以从经济上说,我父母这一辈,除非那1%靠纳税人防老的,普通人还非得靠养儿防老。

当然,我们要一边自己亲自实践赡养老人,一边反对御用砖家鼓吹养儿防老。道理你懂的,我不扯了,扯多了又要站在 我的伟大祖国的 人民的对立面了。

Update: 接受我的伟大祖国的一些网友的批评,为避免伤害我的伟大祖国的人民的感情,将贵国改成了红色的 我的伟大祖国


编程珠玑番外篇 -J. 高级语言是怎么来的-6

Jul 12, 2010

Comments

Scheme 语言是怎么来的 -1

导言

Scheme 是 LISP 的一个方言(dialect)。著名的 SICP 书就是以 Scheme 为教学语言(实际上 SICP 的作者就是 Scheme 的作者)。 虽然 Scheme 本身只是一个精简化的适合教学的语言,可它首先提出的一些重要的思想,引领了新一代的LISP语言的出现。 实际上, LISP 语言发展的历史是连续的,之所以我在这里人为的把 LISP 的发展史划分为上一代和现代,是因为随着 Scheme 首次引入并规范化了一些重要概念, LISP 语言出现了很多以前从来没有大规模普及的新特性。以 Common LISP 为代表的 LISP 语言也因为这些新特性,而焕发了第二春。 人所共知的 Paul Graham 大叔,借着这一波 LISP 复兴的浪潮,不光写出了 On Lisp 这样的好书;而且还用 Common LISP 写出了一个在线电子商务平台,在 1998 年的时候以近 5 千万美元的价格卖给了 Yahoo! (凭借这笔买卖, Paul 大叔现在经营着 Y Combinator 天使投资,成为硅谷著名的天使)。前段时间卖给 Google 的 ITA,负担着世界上大部分的航班资讯查询,核心系统也是 Common LISP。 虽然不该把 Common LISP 的很多成就全部归结到 Scheme, 但 Scheme 作为一个重要的历史分水岭,探究一下它的历史来源还是很有趣的。

函数作为一级对象

我们都知道 LISP 是一个函数式的编程语言。在 LISP 中,函数是一种基本类型。 类比的看,C 家族的语言中,整数是一个基本的类型,所以,整数类型的变量既可以作为参数传递给一个函数,也可以作为返回值返回。比如,两个整数求和这个函数,用 C 家族的语法就是

`#### Scheme 语言是怎么来的 -1

导言

Scheme 是 LISP 的一个方言(dialect)。著名的 SICP 书就是以 Scheme 为教学语言(实际上 SICP 的作者就是 Scheme 的作者)。 虽然 Scheme 本身只是一个精简化的适合教学的语言,可它首先提出的一些重要的思想,引领了新一代的LISP语言的出现。 实际上, LISP 语言发展的历史是连续的,之所以我在这里人为的把 LISP 的发展史划分为上一代和现代,是因为随着 Scheme 首次引入并规范化了一些重要概念, LISP 语言出现了很多以前从来没有大规模普及的新特性。以 Common LISP 为代表的 LISP 语言也因为这些新特性,而焕发了第二春。 人所共知的 Paul Graham 大叔,借着这一波 LISP 复兴的浪潮,不光写出了 On Lisp 这样的好书;而且还用 Common LISP 写出了一个在线电子商务平台,在 1998 年的时候以近 5 千万美元的价格卖给了 Yahoo! (凭借这笔买卖, Paul 大叔现在经营着 Y Combinator 天使投资,成为硅谷著名的天使)。前段时间卖给 Google 的 ITA,负担着世界上大部分的航班资讯查询,核心系统也是 Common LISP。 虽然不该把 Common LISP 的很多成就全部归结到 Scheme, 但 Scheme 作为一个重要的历史分水岭,探究一下它的历史来源还是很有趣的。

函数作为一级对象

我们都知道 LISP 是一个函数式的编程语言。在 LISP 中,函数是一种基本类型。 类比的看,C 家族的语言中,整数是一个基本的类型,所以,整数类型的变量既可以作为参数传递给一个函数,也可以作为返回值返回。比如,两个整数求和这个函数,用 C 家族的语法就是

`

因为在 LISP 里面,函数也成了基本类型。如果我们有一个 add 函数如下:

<br /> (define (add x y) (+ x y))

显然,它在 LISP 里就和 C 里的 int 一样,能够作为参数传递给其他函数。

函数作为参数在 LISP 里非常普遍。 我们知道著名的 APPLY MAP 和 REDUCE 这三个“高阶”函数(所谓高阶的意义就是参数可以是函数)。其中 APPLY 的最基本形式可以带两个参数,第一个参数是函数,第二个参数是一个列表。APPLY 的效果就是把这个 列表 作为参数表,送给第一个参数所代表的函数求值。如果我们在 LISP 里面用 APPLY(add, (1, 2)) 结果就是3,即把 (1,2) 送给add 作为参数,结果自然是 3。 这是函数作为参数的例子,还有函数作为返回值的例子就不一一列举了。

自由变量的幽灵

在 add 这个函数的定义中我们可以看到,它的结果和两个输入值 x, y 有关。 如果我们用 add(1,2) 调用  add 函数, 我们至少期望变量 x 会被赋值为 1, 变量 y 被赋值为 2。而结果 (+ x y) 则相应的为 3。 在这个简单的例子中, 显然,如果 x 和 y 有一个值不知道的话, (+ x y) 的计算就无法完成。我们暂且把这些对函数求值不可缺少的变量称之为“必要变量”。显然,这些必要变量的值是需要确定的,否则函数无法进行求值。在我们 add 函数的例子里,x, y 这两个变量既是全部的必要变量,又是这个函数的参数,所以这个函数的结果就完全由输入的 x, y 的值确定。可以想象,任何一个像 add这样的所有的必要变量都是来自于输入参数的函数,不论在什么地方

被调用,只要输入的参数值一样,输出的结果必然一样。

如果现实中所有的函数都有上面的性质的话,那就没有这篇文章了。可惜的是我们很快发现有好多函数不符合上面我们说的“输入的参数值一样,输出的结果必然一样”这个结论。我们甚至无须用 LISP 里面的例子来说明这一点。用 C 语言的都知道,取系统当前时间的函数 time,以及取随机数的函数 rand, 都是不需要输入值(0个输入参数)。因此任何时候这两个函数被调用的时候,我们都可以认为输入值一样(为 void 或 null)。但我们在不同的时间调用 time 或者多次调用 rand,很显然可以知道他们输出的结果不可能每次一样。

函数式编程里面有更多的类似的例子。这些例子说明了的确有些函数,对于同样的输入值,能够得到不同的结果。这就很显然的表明,这些函数的必要变量中,有些不是函数的输入参数或者内部变量。我们把这些变量,叫做自由变量(free variable) [相反的那些被成为受限变量(bounded variable)]。这里的自由和受限,都是相对函数讲的,以变量的取值是不是由函数本身决定来划分的。

虽然自由和受限变量是函数式语言里面的概念,但在命令式语言中也有影子。比方说,C 语言中,函数中用到的全局变量就是自由变量;在 Java 程序中,匿名内部类里面的方法可以用到所在外部类中的成员变量或者所在方法里标记为 final 的那些变量。这些可以被内部类的方法访问的,又不在内部类方法的参数表里面的变量都是自由变量。乐意翻看 GNU C Library 的好奇读者会看到,GNU libc 中的 rand 函数就用到了一个 random_data 的变量作为自由变量 (glibc/stdlib/random.c)。 time 也是一样,通过一个系统调用来设置时间,而这在原理上等价于用到一个叫做”当前时间”的自由变量 (glibc/stdlib/time/time.c)。

我们知道,在高级语言里面仅仅设计或者加入一个特性不难,难的是让所有的特性能协调一致的工作。比方说 Java 语言假设一切均为为对象,容器类型也假设装着对象,但是 int 类型却不是对象,让无数程序员为装箱拆箱大汗淋漓。 回到 LISP, 当函数允许自由变量,函数有能够被作为参数传来传去的时候,自由变量的幽灵就随着函数作为参数传递而在程序各处游荡。这就带来了两个问题,一个涉及到自由变量的值的确定机制,另一个涉及到这个机制的实现。

**

两种作用域**

为了说明自由变量的幽灵和作用域,我们还是从一个例子入手。假设我们要一个做加 n 的函数。为了体现出自由变量,我们把它写成

``#### Scheme 语言是怎么来的 -1

导言

Scheme 是 LISP 的一个方言(dialect)。著名的 SICP 书就是以 Scheme 为教学语言(实际上 SICP 的作者就是 Scheme 的作者)。 虽然 Scheme 本身只是一个精简化的适合教学的语言,可它首先提出的一些重要的思想,引领了新一代的LISP语言的出现。 实际上, LISP 语言发展的历史是连续的,之所以我在这里人为的把 LISP 的发展史划分为上一代和现代,是因为随着 Scheme 首次引入并规范化了一些重要概念, LISP 语言出现了很多以前从来没有大规模普及的新特性。以 Common LISP 为代表的 LISP 语言也因为这些新特性,而焕发了第二春。 人所共知的 Paul Graham 大叔,借着这一波 LISP 复兴的浪潮,不光写出了 On Lisp 这样的好书;而且还用 Common LISP 写出了一个在线电子商务平台,在 1998 年的时候以近 5 千万美元的价格卖给了 Yahoo! (凭借这笔买卖, Paul 大叔现在经营着 Y Combinator 天使投资,成为硅谷著名的天使)。前段时间卖给 Google 的 ITA,负担着世界上大部分的航班资讯查询,核心系统也是 Common LISP。 虽然不该把 Common LISP 的很多成就全部归结到 Scheme, 但 Scheme 作为一个重要的历史分水岭,探究一下它的历史来源还是很有趣的。

函数作为一级对象

我们都知道 LISP 是一个函数式的编程语言。在 LISP 中,函数是一种基本类型。 类比的看,C 家族的语言中,整数是一个基本的类型,所以,整数类型的变量既可以作为参数传递给一个函数,也可以作为返回值返回。比如,两个整数求和这个函数,用 C 家族的语法就是

`#### Scheme 语言是怎么来的 -1

导言

Scheme 是 LISP 的一个方言(dialect)。著名的 SICP 书就是以 Scheme 为教学语言(实际上 SICP 的作者就是 Scheme 的作者)。 虽然 Scheme 本身只是一个精简化的适合教学的语言,可它首先提出的一些重要的思想,引领了新一代的LISP语言的出现。 实际上, LISP 语言发展的历史是连续的,之所以我在这里人为的把 LISP 的发展史划分为上一代和现代,是因为随着 Scheme 首次引入并规范化了一些重要概念, LISP 语言出现了很多以前从来没有大规模普及的新特性。以 Common LISP 为代表的 LISP 语言也因为这些新特性,而焕发了第二春。 人所共知的 Paul Graham 大叔,借着这一波 LISP 复兴的浪潮,不光写出了 On Lisp 这样的好书;而且还用 Common LISP 写出了一个在线电子商务平台,在 1998 年的时候以近 5 千万美元的价格卖给了 Yahoo! (凭借这笔买卖, Paul 大叔现在经营着 Y Combinator 天使投资,成为硅谷著名的天使)。前段时间卖给 Google 的 ITA,负担着世界上大部分的航班资讯查询,核心系统也是 Common LISP。 虽然不该把 Common LISP 的很多成就全部归结到 Scheme, 但 Scheme 作为一个重要的历史分水岭,探究一下它的历史来源还是很有趣的。

函数作为一级对象

我们都知道 LISP 是一个函数式的编程语言。在 LISP 中,函数是一种基本类型。 类比的看,C 家族的语言中,整数是一个基本的类型,所以,整数类型的变量既可以作为参数传递给一个函数,也可以作为返回值返回。比如,两个整数求和这个函数,用 C 家族的语法就是

`

因为在 LISP 里面,函数也成了基本类型。如果我们有一个 add 函数如下:

<br /> (define (add x y) (+ x y))

显然,它在 LISP 里就和 C 里的 int 一样,能够作为参数传递给其他函数。

函数作为参数在 LISP 里非常普遍。 我们知道著名的 APPLY MAP 和 REDUCE 这三个“高阶”函数(所谓高阶的意义就是参数可以是函数)。其中 APPLY 的最基本形式可以带两个参数,第一个参数是函数,第二个参数是一个列表。APPLY 的效果就是把这个 列表 作为参数表,送给第一个参数所代表的函数求值。如果我们在 LISP 里面用 APPLY(add, (1, 2)) 结果就是3,即把 (1,2) 送给add 作为参数,结果自然是 3。 这是函数作为参数的例子,还有函数作为返回值的例子就不一一列举了。

自由变量的幽灵

在 add 这个函数的定义中我们可以看到,它的结果和两个输入值 x, y 有关。 如果我们用 add(1,2) 调用  add 函数, 我们至少期望变量 x 会被赋值为 1, 变量 y 被赋值为 2。而结果 (+ x y) 则相应的为 3。 在这个简单的例子中, 显然,如果 x 和 y 有一个值不知道的话, (+ x y) 的计算就无法完成。我们暂且把这些对函数求值不可缺少的变量称之为“必要变量”。显然,这些必要变量的值是需要确定的,否则函数无法进行求值。在我们 add 函数的例子里,x, y 这两个变量既是全部的必要变量,又是这个函数的参数,所以这个函数的结果就完全由输入的 x, y 的值确定。可以想象,任何一个像 add这样的所有的必要变量都是来自于输入参数的函数,不论在什么地方

被调用,只要输入的参数值一样,输出的结果必然一样。

如果现实中所有的函数都有上面的性质的话,那就没有这篇文章了。可惜的是我们很快发现有好多函数不符合上面我们说的“输入的参数值一样,输出的结果必然一样”这个结论。我们甚至无须用 LISP 里面的例子来说明这一点。用 C 语言的都知道,取系统当前时间的函数 time,以及取随机数的函数 rand, 都是不需要输入值(0个输入参数)。因此任何时候这两个函数被调用的时候,我们都可以认为输入值一样(为 void 或 null)。但我们在不同的时间调用 time 或者多次调用 rand,很显然可以知道他们输出的结果不可能每次一样。

函数式编程里面有更多的类似的例子。这些例子说明了的确有些函数,对于同样的输入值,能够得到不同的结果。这就很显然的表明,这些函数的必要变量中,有些不是函数的输入参数或者内部变量。我们把这些变量,叫做自由变量(free variable) [相反的那些被成为受限变量(bounded variable)]。这里的自由和受限,都是相对函数讲的,以变量的取值是不是由函数本身决定来划分的。

虽然自由和受限变量是函数式语言里面的概念,但在命令式语言中也有影子。比方说,C 语言中,函数中用到的全局变量就是自由变量;在 Java 程序中,匿名内部类里面的方法可以用到所在外部类中的成员变量或者所在方法里标记为 final 的那些变量。这些可以被内部类的方法访问的,又不在内部类方法的参数表里面的变量都是自由变量。乐意翻看 GNU C Library 的好奇读者会看到,GNU libc 中的 rand 函数就用到了一个 random_data 的变量作为自由变量 (glibc/stdlib/random.c)。 time 也是一样,通过一个系统调用来设置时间,而这在原理上等价于用到一个叫做”当前时间”的自由变量 (glibc/stdlib/time/time.c)。

我们知道,在高级语言里面仅仅设计或者加入一个特性不难,难的是让所有的特性能协调一致的工作。比方说 Java 语言假设一切均为为对象,容器类型也假设装着对象,但是 int 类型却不是对象,让无数程序员为装箱拆箱大汗淋漓。 回到 LISP, 当函数允许自由变量,函数有能够被作为参数传来传去的时候,自由变量的幽灵就随着函数作为参数传递而在程序各处游荡。这就带来了两个问题,一个涉及到自由变量的值的确定机制,另一个涉及到这个机制的实现。

**

两种作用域**

为了说明自由变量的幽灵和作用域,我们还是从一个例子入手。假设我们要一个做加 n 的函数。为了体现出自由变量,我们把它写成

``

这个函数本身没什么特别的:输入一个 s, 输出一个 对任意 x 返回 x+s 的函数。注意到这个函数的“返回值”是一个函数。 基于这个 addn 函数,我们可以定义 +1 函数 add1 函数如下,

<br /> (define (add1 s) ((addn 1) s))

这个也很好解释,如果输入一个 s, (addn 1) 返回了一个加一函数,这个函数作用在 s 上,即可得到 s+1。一切看上去很顺利,直到我们用一个Scheme 出现前的 LISP 解析器去计算 (add1 4)。 我们期望得到的值是 5, 而它给你的值可能是 8。怎么回事?

为了解释这个 8 的来源,我们可以模拟一下一个基于栈的解释器的工作过程。(add1 4) 调用首先将参数 s 赋值为 4 然后,展开 add1 函数,即将 s=4 压栈,计算 (addn 1)。在调用 addn 时。s 又作为了 addn 的形式参数。因此,按照基于栈的解释器的标准做法,我们在一个新的活动窗口中将 s =1 压栈。addn 这个函数返回的是一个 “lambda x (+ x s)” 的函数,其中 s 是自由变量。 然而一旦 addn 返回,栈中的 s=1 就会被弹出。当我们把这个返回了的 lambda 表达式作用到 4 上求值时候,x 是这个 lambda 表达式传入的形式参数,赋值为 4,栈里面的 s 的值 只有 s=4, 因此 (+ x s) 得到的是 8。

这显然不是我们想要的。总结这个结果错了的原因,是因为我们的解释器没有限定 lambda x (+ x s) 里面的自由变量 s 为 1。 而是在计算这个 lambda 表达式的时候才去查找这个自由变量的值。 自由变量的幽灵在函数上开了一个后门,而我们没有在我们想要的地方堵上它,让它在函数真正求值的时候泄漏出来。

我们不是第一个发现这个问题的人。 实际上, LISP 刚出来没多久,就有人向 LISP 的发明人 John McCarthy 报告了这个 “BUG”。 John 也认为这是一个小 BUG,就把球踢给了当时写 LISP 实现的 Steve Russell。此人我之前的文章介绍过,乃是一个水平很高的程序猿(Code Monkey)。他认识到,这个问题的来源,在于返回的 lambda 表达式失去了不应该失去的确定它自由变量值的环境信息,在求值的时候,这些环境信息应该跟着这个 lambda 表达式一起。这样才能保证没有这个 BUG。不过 lambda 表达式在 LISP 语言中已经成型了,所以他就引入了一个新叫做 FUNCTION 的修饰符。作为参数的 lambda 表达式或函数要改写成 (FUNCTION lambda) 。 这样,这个 lambda 表达式在被 eval 解析的时候就会被标记成 FUNARG,并且静态绑定到解析时所在环境。而用 APPLY 对函数求值时,有 FUNARG 标签的函数会在当时绑定的环境中求值,而不是在当前环境中求值。自由变量没有到处乱跑,而是被限制在了当时绑定的环境里面。 Russell 的这个巧妙设计,成功关闭了自由变量在函数上开的口。这种加上了环境的函数就既能够被四处传递,而不需要担心自由变量的幽灵到处乱串。 这个东西,后来就被称为“闭包”。Russell 用 FUNCTION,以用一种“装饰”的方式,在 LISP 1.5 中第一次引入和实现和闭包。

在编程语言的术语中,上面的让自由变量自由自在的在运行时赋值的机制,一般叫做动态作用域(dynamic scope),而让函数和确定自由变量值在解析时静态绑定的机制,一般称之为静态作用域(static dynamic scope)。既然是静态绑定的环境是解析的时候确定的,而解析器是逐行解析程序的,所以,静态作用域的环境是完全由程序代码的结构确定的。因此有时候静态作用域又被等价的称为“文法作用域”(lexical scope)。上面我们的例子里。我们的真正意图是使用静态作用域,却遇到了一个使用动态作用域的 LISP 解析器,因此出现了 (add1 4) 等于 8 的错误。 但这个问题并不足以说明静态作用域一定好。动态作用域的问题,关键在于违反了 Alpha 变换原则和封装原则,不过不在此详细展开了。

后续的几小节提要

著名的 FUNARG 问题

**

Actor 计算模型的启示**

**

LISP 也面向对象?**

**

编译,编译,优化,优化**