<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>4G spaces &#187; pearl</title>
	<atom:link href="http://blog.youxu.info/category/pearl/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.youxu.info</link>
	<description>I am Eric Xu, a Googler.</description>
	<lastBuildDate>Fri, 03 Feb 2012 06:31:19 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=</generator>
		<item>
		<title>编程珠玑番外篇 -M. 软件工具的设计哲学1</title>
		<link>http://blog.youxu.info/2012/02/02/software-tools-1/</link>
		<comments>http://blog.youxu.info/2012/02/02/software-tools-1/#comments</comments>
		<pubDate>Fri, 03 Feb 2012 01:04:44 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[CompSci]]></category>
		<category><![CDATA[pearl]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=1243</guid>
		<description><![CDATA[我们通过工具的设计者和使用者的视角观察工具，讨论工具背后的设计哲学和对应的学习曲线。 设计者和使用者 从某种意义上来说，工具是个联系使用者和设计者的桥梁。工具的设计者为解决... ]]></description>
			<content:encoded><![CDATA[<p>我们通过工具的设计者和使用者的视角观察工具，讨论工具背后的设计哲学和对应的学习曲线。</p>
<p><strong>设计者和使用者</strong></p>
<p>从某种意义上来说，工具是个联系使用者和设计者的桥梁。工具的设计者为解决一类问题而设计工具，工具的使用者也是为了解决一类问题而使用工具的。这两者对于工具都有自己的理解。工具的设计者通常会预测此工具会被用来解决怎样的问题，在设计工具时预见使用者的偏好，而工具的使用者则根据所面临的问题选择合适的工具。</p>
<p>在理想情况下，设计者和使用者对问题的理解 (Vision) 是类似或者重叠的。这时候，工具的设计者能够完全理解使用者的需求，而使用者完全理解这个工具的长处和短处。成语“得心应手”说的就是这样的一种情况：掌握了一样工具，因此使用起来非常顺利。</p>
<p>在现实中，设计者和使用者对问题的理解可能是有差异的，随之对工具的使用方法的理解也有所不同。有时候，工具的使用者能够将工具用到远超出设计者想象的地步。有两个著名的例子可以说明这个问题。第一个例子是出自 Bell 实验室的 AWK 语言。从 AWK 的三位作者的角度看，这是一个与 sed 互补的，处理文本记录的语言。而AWK在贝尔实验室内部推广开来后，三位作者发现其他团队用 AWK 来写作大的数据管理系统，而三位作者本人从来没有写过超过一百行的 AWK 脚本。第二个例子是 C++ 的模版　(template)。模版的设计者的初衷是让 C++ 在处理对象时更加灵活，而自从C++模版的图灵完全特性被发现后，写作一些通用的库成为可能。如C++的Boost库，即重度依赖于模版。设计模版的人未必想到 Boost 库那样对模版的奇妙运用。这两个例子，都充分说明了工具的设计者未必能够充分体验到自己设计的工具的强大之处。</p>
<p>另一种情况是工具的设计者是解决问题的专家，对问题有深刻的理解，而工具的使用者则没有能够完全体验到工具的强大。工具的使用者因此需要学习如何使用工具，学会像工具的设计者那样思考，从而领略到工具的强大之处，达到得心应手的地步。对于完成学校教育后的成人来说，大多数学习都是在学习工具的使用。那么，如何学习软件工具呢？</p>
<p>在我看来，虽然工具千差万别，学习工具的用法也大相径庭，不过如果细细总结，工具的设计者所奉行的套路无非就两种，所对应的学习方法也有两种。我把这两种设计讨论形象地称为<strong>瑞士军刀</strong>和<strong>工具链</strong>。这两种设计哲学背后的假设，以及相应的学习曲线是不同的。</p>
<p><strong>设计者的哲学一：瑞士军刀</strong></p>
<p>瑞士军刀以方便著称，一把军刀包含了很多小部件，配合使用能够解决很多野外生存问题。不少计算机工具借用了这个类比。在Google 上搜索 “The swiss army knife of”　会发现很多软件工具号称某领域的瑞士军刀（见表一）</p>
<div dir="ltr">
<table border="1">
<colgroup>
<col width="*" />
<col width="*" />
<col width="*" /></colgroup>
<tbody>
<tr>
<td>工具名称</td>
<td>用途领域</td>
<td>广告词</td>
</tr>
<tr>
<td>BusyBox</td>
<td>嵌入式 Linux 命令集</td>
<td>The Swiss Army Knife of Embedded Linux</td>
</tr>
<tr>
<td>Perian</td>
<td>Apple QuickTime 组件管理</td>
<td>Perian &#8211; The swiss-army knife of QuickTime® components</td>
</tr>
<tr>
<td>Sox</td>
<td>音频文件编辑</td>
<td>The Swiss Army knife of sound processing programs</td>
</tr>
<tr>
<td>Netcat</td>
<td>TCP/IP 数据包分析</td>
<td>TCP/IP Swiss Army Knife</td>
</tr>
<tr>
<td>FFMpeg</td>
<td>视频和视频流文件编/解码</td>
<td>The swiss army knife of Internet Streaming</td>
</tr>
<tr>
<td>Kanif</td>
<td>计算机集群管理</td>
<td>Cluster management and administration Swiss Army knife</td>
</tr>
</tbody>
</table>
</div>
<p>从表一我们不难发现，这些宣称为“瑞士军刀”的工具有一个共同特点：都是针对某个特定领域的问题而设计的。这一点和瑞士军刀是相似的。瑞士军刀的多功能，并不是指这是一把砍树修桥盖房子的万能刀，而是说在野外生存这个领域内，军刀能够解决许多问题。回到表一，用过这些工具的读者一定有所体会：这些工具解决所在领域的复杂问题的能力是非常强的。比如，FFMpeg 内置多种视频编、解码器，众多格式能够相互转换，支持的视频编码格式比任何商业软件都要多。这些优势让 FFMpeg 几乎能解决所有现实中遇到的视频处理问题。因为这些工具针对特殊领域，且功能强大，用瑞士军刀做比喻是很形象的。</p>
<p>除了自称为“军刀”的这些工具外，还有许多耳熟能详的工具属于此类。如平面设计软件 Photoshop，是融许多复杂图像算法和插件于一体的图像处理之瑞士军刀；GNU 的 GCC 编译器，是集语法分析，代码优化，代码生成等功能于一体的专门负责程序编译的瑞士军刀。这些软件包的内部结构，都不是一个单一的程序，单一的部件，而是一揽子的部件，一箩筐的功能模块。</p>
<p>瑞士军刀巧妙地将众多小工具紧凑地组合到了一起。同样，我们说的军刀工具的一大优雅之处也是类似的，即通过一个统一的控制界面，将这一揽子的部件有机地组合在了一起。设计模式里，这种组织功能的方式称作为 Facade 模式，具体指以一个命令或者一个统一的界面抽象内部复杂的操作。如 FFMpeg 和 GCC 这样几乎无所不能的软件包，都可以通过一个单一的命令 (ffmpeg 或 gcc) 加不同的参数调用。在 GUI 程序的世界，所有的 Photoshop 插件都可以通过 Photoshop 的图形界面菜单调用。Facade 模式隔开了用户和“军刀&#8221;工具复杂的内部逻辑，让用户从一个&#8221;抽象&#8221;的层面理解工具。正如我们开车并不一定要了解引擎是怎么工作一样，工具的使用者不需要关系这些工具的内部是如何运转的。</p>
<p>总结这类工具的特点，瑞士军刀是一种把各种工具组合到一起，一起解决一个复杂问题的设计哲学。核心是。设计模式里的facet 模式，一个领域的问题，而不是一个特定的工具。这里实际上是一类工具，而非一个单独的工具。这一类工具被精巧地安排在一起，对外提供一个一致的服务接口，让用户以简单的控制方式完成相对复杂的任务。</p>
<p><strong>领域知识假设和学习曲线</strong></p>
<p>“瑞士军刀“工具都是面向特定领域的。无论是图片，视频处理，还是查看TCP包信息，这些工具都假设了使用者知道图片处理，视频处理或者TCP包的结构等知识。心理学上把这些为了解决特定领域问题而所需的知识叫做”领域模型“(domain model)。工具的设计者是不负责教授这些知识的。因此，作为工具的使用者，掌握这些特定领域的知识就成了使用工具的前提。一般的软件都是基于领域模型的。比如，平面图形处理工具 Photoshop 假设图片是个多层次的物体，如果我们不了解这个模型，就完全不知道为啥这个软件为啥比系统自带的 Paint 复杂几千倍。</p>
<p>因为军刀背后蕴含着领域知识假设，学习军刀工具就不仅仅是学习工具的使用本身，而是学习背后的领域知识。如果不了解背后的领域知识，即使我们会操作这样工具，依然谈不上是个熟练的使用者。相信诸位读者都属于“懂计算机”的一类人。现实中，我们这类人常常因为“懂计算机”，而被家人和朋友要求编辑图像，做个视频或者做个网页等等。有些要求还特别专业，让只会写程序的人非常吃力。究其原因，是因为程序员群体掌握的，只是编程的领域知识。如图像，视频处理所需要的领域知识，均是编程之外的了。即使像 Eclipse 这样专门给程序员用的软件，也隐含假设了使用者需要重构，设计模式等等的知识。不掌握这些知识的程序员用 Eclipse 也仅仅是将其当成一个花哨的编辑器使用而已。</p>
<p>因为需要领域知识做铺垫，军刀工具的学习曲线是因人而异的。对于有的人来说，如果本来就知道工具背后的假设，学习计算机操作就毫不费力。以我爸爸为例，本身用计算机并不熟练，但他学过画画，作图感一流，若干年前从书店随便买了一本 Photoshop 入门后就把 Photoshop 玩得很熟。而我知道这个软件每个选项的意义，却处理不出什么像样的图。对于我爸来说，他的学习曲线就很平缓，而对于我来说，就很陡峭。</p>
<p>学习曲线不一样这一点在计算机语言的学习上尤为明显（计算机语言是众多编程特性的大糅合，是典型的军刀设计）。像 Google 这样的公司面试时完全不考察语言细节，很多人入职的时候都不会 Java。因为我们相信任何合格的程序员都能很快学会 Java 这个语言。道理很简单，只要考察一个人掌握了Java 语言背后的领域知识，即面向对象的设计，数据结构，控制结构等等，就有充分理由相信这个人学 Java 毫不费力。但是，话说回来，每个语言背后的领域假设都有细微的差别。比如 Lua 只支持哈希表，Awk 支持哈希表也支持记录处理，Vim 里一切都是文本对象等等。真的领会这些领域模型并且将这些语言用得纯熟，没有长时间的投入是不可能的，这就是为何 Peter Norvig 强调“十年学会程序设计”。这两种对待编程语言的态度是不矛盾的。引申出来，对于语言或者工具的初学者来说，要分情况学习军刀工具。在不熟悉领域知识的情况下，应该找一本非常简单的书熟悉领域模型，获得一个 Big picture，而非上来就想着要解决手头的问题。而对于已经熟悉领域模型的人，比如会了一个 MVC 框架再学另一个的人，随便捡起一本 In Action 或者 Cookbook 也能事半功倍；有人甚至书都不需要买，看看文档就可以了。不清楚内情的可能会把这类人叫做强人，其实了解了背后的底细后，所有人都可以如此。</p>
<p>最后谈一下军刀工具的设计策略。我们说了，军刀工具背后蕴含的是领域知识。因此，设计军刀工具的人，必然需要是对问题有深刻理解的领域专家。这一点很好理解，因为很难想象一个不懂图像处理的人会指导 Photoshop 的设计，或者一个不懂统计的人会设计 R。不过，即使是最资深的专家，也未必能设计出一个能覆盖到所有问题的工具。这时候，工具的扩展性就成了一个重要的考量。可以这么说，几乎所有成熟的军刀工具，最后一定会进化出一个插件系统，用以扩展功能。插件系统的好与坏是考验设计者功力的时候。像 Firefox 和 Photoshop 这样的工具，若不是第三方插件，至多是一个普通的软件。加入插件系统后，这些工具成为了一个平台，从而衍生出了无数的新用例。即使像功能小巧和 Emacs 格格不入的 Vim, 最终也进化出了插件系统。这是一样工具成熟的体现。</p>
<p>总结一下军刀工具的特点如下：</p>
<ul>
<li> 面向特定领域，覆盖该领域大部分问题</li>
<li>由领域专家设计，使用者需要了解该领域的基本知识才能熟练运用工具</li>
<li>内部由众多小模块组成，对外提供一致，简单的控制界面</li>
<li>常常具有强大的插件系统，以方便用户扩展现有系统</li>
</ul>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2012/02/02/software-tools-1/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>编程珠玑番外篇 -K. 高级语言是怎么来的-7</title>
		<link>http://blog.youxu.info/2011/09/27/lisp-prehistory/</link>
		<comments>http://blog.youxu.info/2011/09/27/lisp-prehistory/#comments</comments>
		<pubDate>Tue, 27 Sep 2011 21:49:28 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[CompSci]]></category>
		<category><![CDATA[pearl]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=1213</guid>
		<description><![CDATA[LISP 语言前传 Lisp 的主要设计者 John McCarthy 曾经就 Lisp 的发展史，专门写过一篇 History of Lisp 的文章。这里介绍的历史，基本史实部分参照了 John McCarthy 的这篇文章，以及同时期 MIT 的关于 Lisp ... ]]></description>
			<content:encoded><![CDATA[<div>
<h2 id="internal-source-marker_0.7188789076171815" dir="ltr">LISP 语言前传</h2>
<p>Lisp 的主要设计者 John McCarthy 曾经就 Lisp 的发展史，专门写过一篇 <a href="http://www-formal.stanford.edu/jmc/history/lisp/lisp.html">History of Lisp</a> 的文章。这里介绍的历史，基本史实部分参照了 John McCarthy 的这篇文章，以及同时期 MIT 的关于 Lisp 的技术报告。</p>
<p>Lisp 的历史要从 IBM 的神奇机器 704 说起。此时是 1954 年，尽管距离 1946 年第一台计算机 ENIAC 的出现已经八年了，商用计算机市场还仅仅起步。很早就进入计算机市场的 IBM 做出了一个影响深远的决定：发布一台可以进行浮点计算的，面向科学和工程的电子计算机。这台计算机，很朴素地跟着 IBM 之前发布的 701，702 后，被编号成 704（不知为什么 IBM 从来没公布过 703）。说 704 是神奇机器，是因为这台机器在计算机科学发展史上意义重大：世界上最早的语音合成程序就是由 Bell 实验室的科学家在 IBM 704 上完成的。 Fortran，Lisp 也是最早在 IBM 704 上实现的。</p>
<p>和当年的所有计算机一样，IBM 704 是个百万美元级别的大玩具，不是一般人甚至一般大学能够买得起的。好在 IBM 和大学的关系一向很紧密，在 1957 年的时候，决定捐一台 704 给 MIT。当时在 Dartmouth 教书的 John McCarthy 和在 MIT 教书的 Marvin Minsky 关系很好，因此这台即将到达的 704，即将成为 McCarthy 的新玩具。</p>
<p>当年部署一台计算机的周期很长，为了不让自己闲着，McCarthy 决定一边等机器部署，一边研究一下如果有了这台机器，可以做点什么。当时 Minsky 手里有一个 IBM 的项目，内容是使用计算机证明平面几何问题。既然计算机没来不能写程序，他们就只能从抽象的层面思考问题的解决方法。这个思考的结果，是开发一套支持符号计算的 Fortran 子系统。他们的基本想法是，用一系列的 FORTRAN 子程序，来做逻辑推理和符号演绎。</p>
<p>回头看，这条路的确绕开了没有 704 就写不了程序的路障。因为我们只需要大致了解 Fortran 能够做什么，不能做什么，无需实际 Fortran 编程，就可以假想我们已经有了一系列未来可以实现的子程序，然后只要在数学上证明这些通过子程序的组合，加上自动逻辑推理，就可以证明平面几何定理。这就把一个计算机工程学问题，抽象成了一个数学问题（日后这个领域被正式划归到人工智能的学科中，但在当时这还是属于数学问题）</p>
<p>这样，计算机没来之前，McCarthy 的最终结果，是一个用 Fortran 子程序做列表处理的简单系统。McCarthy 的这条路很现实的做法——如果不用 Fortran 而是自己写一个新的语言的编译器话，可能需要好几年的时间。而 McCarthy 当年还不是终身教授，投入到写作新语言这样旷日持久且不能保证成果的项目中去，不会对他的职业生涯有太大的正面作用。</p>
<p>704 送到 MIT 后， McCarthy 带着两个研究生，将之前计划的 Fortran 列表处理子程序实现了，并命名为 Fortran 列表处理语言 (FLPL) 。然而，因为 Fortran 语言本身的限制，McCarthy 对 FLPL 并不满意。他在写作自动求函数导数的程序时[a]，发现 FLPL 的弱点集中体现在两个地方。</p>
<p>第一个问题是递归。我们在 Fortran 语言怎么来的这一节已经提到，704 上的 Fortran 语言是不支持递归的。而 McCarthy 心中所设想的语言，很重要的一条就是递归：没有递归，很多列表处理的函数只能用循环来实现，而循环本身并不是 McCarthy 设想的语言的一部分。熟悉函数求导的链式法则的读者可以想像，没有递归的求导程序是何其难写，因为函数求导过程本身就是递归定义的。</p>
<p>第二个问题是 Fortran 的 IF 语句。IF 家族的分支语句，在计算机程序设计中可以说必不可少。在 McCarthy 那里 IF 就更重要了，因为几乎所有有递归函数的地方就有 IF（因为递归函数需要 IF 判断结束条件）。相信读者都很熟悉这种 IF 结构</p>
<pre style="padding-left: 30px;" dir="ltr">IF 条件 THEN</pre>
<pre style="padding-left: 30px;" dir="ltr">    一些语句;</pre>
<pre style="padding-left: 30px;" dir="ltr">ELSE</pre>
<pre style="padding-left: 30px;" dir="ltr">    另一些语句;</pre>
<pre style="padding-left: 30px;" dir="ltr">END IF</pre>
<p>这是从 ALGOL 语言一脉相承下来的，很“自然”的 IF 写法。而早期的 FORTRAN 的 IF 写法却不这么直观，而是</p>
<pre style="padding-left: 30px;" dir="ltr">IF (表达式) A B C</pre>
<p>取决于表达式的值是小于零，等于零还是大于零，分别跳到（等价于 goto）标签 A， 标签B 或者标签 C。这个 IF 隐含了三个 Goto，可以说和结构化编程的实践截然相反，降低了程序的可读性。 Fortran 首创的这个三分支跳转的 IF 饱受诟病，Fortran 77 开始支持结构化的 IF，而 Fortran 90 标准进一步宣布三分支跳转的用法已经“过时”，不支持使用。</p>
<p>在 McCarthy 那里，Fortran 的三分支跳转 IF 也不方便。为此，在 FLPL 中，他试图用一个叫 XIF 子程序完成类似于 IF 的分支功能，用法是：</p>
<pre style="padding-left: 30px;" dir="ltr">XIF(条件, 表达式A, 表达式B)</pre>
<p>取决于条件的满足与否，XIF 返回表达式A 或者表达式B 的值。很快，他发现，用子程序的方法实现 XIF，在语义上并不正确。我们知道，在 Fortran 和其他高级语言中，函数参数的值在进入函数之前必须全部确定。在 XIF 这里，不难看出，不管条件满足与否，我们都先要计算表达式A 和表达式B 的值。而 IF 是个分支逻辑，从语义上来说，应该只计算满足条件的分支的值。因此，用函数来实现 IF 是不正确的 [b]。</p>
</div>
<p style="padding-left: 30px;"><span style="color: #003366;">作为一个旁注，尽管 John McCarthy 早在50多年前就发现了函数实现 IF 是语义错误的，现代的程序员还常常犯这个错误。一个值得一题的例子是 C++ 逻辑运算符重载和短路表达式的不等价性。我们都知道，在 C 语言中，逻辑与 (&amp;&amp;) 和逻辑或( || ) 都隶属于短路表达式，也就是说，对于 A &amp;&amp; B 这样的表达式，如果 A 已经确定为 false，就无需计算表达式 B 的值，即 B 的计算被”短路”。以 C 为蓝本的 C++ 一方便保留了这些短路表达式，另一方面在面向对象的特性中，引入了运算符重载。具体来说，只要一个对象定义了 operator&amp;&amp; 成员函数，就可以进行 &amp;&amp; 运算。乍一看，这是一个很酷的特性，可以让程序员用 A&amp;&amp;B 这样的数学表达式表达复杂的逻辑关系。然而，仔细想想，  A.operator&amp;&amp;(B) 在语义上并不等价于 C 所定义的 A&amp;&amp;B，原因在于 A.operator&amp;&amp;() 是个函数，在求值之前需要先计算 B 的值，而后者是个短路表达式，本质上相当于 </span></p>
<pre style="padding-left: 30px;" dir="ltr"><span style="color: #003366;">IF A:</span></pre>
<pre style="padding-left: 30px;" dir="ltr"><span style="color: #003366;"> return True</span></pre>
<pre style="padding-left: 30px;" dir="ltr"><span style="color: #003366;">ELSE:</span></pre>
<pre style="padding-left: 30px;" dir="ltr"><span style="color: #003366;"> return B</span></pre>
<pre style="padding-left: 30px;" dir="ltr"><span style="color: #003366; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">因为短路表达式不一定会对 B 求值，这两者从语义上就是不等价的。如果 B 不是一个简单的对象，而是一个复杂表达式的时候，对 B 求值可能有副作用，而这个副作用，是写 A &amp;&amp; B 并把它当做短路表达式的程序员所没有预见的。按照 C++ Gotcha 的说法，这很容易造成潜在的程序 Bug。实际上，C++逻辑运算符重载是一个危险的特性，很多公司的编程标准都禁止使用逻辑运算符重载。</span></pre>
<p>递归和 IF 两个问题，使得 Fortran 从本质上就不符合 McCarthy 期望，以 Fortran 为宿主的开发列表处理语言也不可能达到 McCarthy 想要的结果。因此，唯一的解，就是抛开 Fortran，从头开始，设计一个新的语言。值得注意的是，此时 McCarthy 正好跳槽到了 MIT 做助理教授，MIT 有足够多的编程强人，可以帮 McCarthy 完成这个编写新语言的任务。这个强人，就是 Steve Russell；这个语言，就是 Lisp。</p>
<p>[a] 读过 SICP 的读者会发现这是一道习题<br />
[b] 读过 SICP 的读者会发现这又是一道习题</p>
<p>&nbsp;</p>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2011/09/27/lisp-prehistory/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>编程珠玑番外篇-K. Plan 9 的故事（修订版）</title>
		<link>http://blog.youxu.info/2011/03/21/story-of-plan-9-revised/</link>
		<comments>http://blog.youxu.info/2011/03/21/story-of-plan-9-revised/#comments</comments>
		<pubDate>Mon, 21 Mar 2011 18:12:42 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[pearl]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=1188</guid>
		<description><![CDATA[（本文是对于之前编程珠玑番外篇系列中 Plan 9 的八卦这一篇的彻底修订，本文得到了博文视点的卢鸫翔编辑的很多帮助） 计算机发展史上，创新性产品层出不穷。其中，有些想法和产品在技... ]]></description>
			<content:encoded><![CDATA[<div><em>（本文是对于之前编程珠玑番外篇系列中 <a href="http://blog.youxu.info/2008/10/06/about-plan/">Plan 9 的八卦</a>这一篇的彻底修订，本文得到了博文视点的卢鸫翔编辑的很多帮助）</em></div>
<div></div>
<div>计算机发展史上，创新性产品层出不穷。其中，有些想法和产品在技术上很先进，却很遗憾的没有获得广泛的接纳和商业的成功。不过，只要时机到来，这些创新，往往都会以新的面目再次复兴。</p>
<p>这样的例子在历史中屡见不鲜。Jobs 和 Apple 分手后开创的 NeXT 公司的操作系统和硬件设备，创新点很多，市场反响却不大。而 NeXT 系统在软件和硬件设计上的创新，以及工业设计的思想，最终成为了现在 iOS 系统、Mac 系统软硬件设计的基石。同样是 Apple, 当年出品的 Hypercard 软件，首创了超文本格式和交互式页面。虽然 HyperCard 的这些创新在当时并不显得太出众，最终被苹果终止开发。但到了 Internet 出现之后，Tim Berners-Lee 受到这种“超文本 (hypertext)”格式的启发，将这些思想平移到联网的计算机上，由此出现了万维网。Ward Cunningham 更是受这一张一张的“数字卡片”受到，发展出了世界上第一个 wiki 系统 WikiWikiWeb。所有的这些例子，都说明了一项当时未必得到大多数人认可的创新可能会在意想不到的地方凤凰重生。 本节我们介绍的 Plan 9 操作系统，也是这样的一个例子。</p></div>
<div>
<h3>FTPFS 虚拟文件系统</h3>
<p>大部分读者应该都用过 FTP 在两台机器之间传送文件。FTP 是一种简单成熟的传输协议。试想现在我们需要修改一个 FTP 服务器上的文件，我们无法直接用本机上的编辑器打开这个远程的文件编辑，而需要先下载这个文件，修改后再上传。这种编辑远程文件的方法显然比编辑本机的文件麻烦多了。 我们退后一步仔细想一下这个不方便，会发现，文件是同样的一个文件，只是因为文件不在本地，我们需要借助于 FTP 协议访问，所以我们就不能直接编辑它了（事实上有些功能强大的编辑器如 VIM 仍然可以编辑，但普通的编辑器则不能）。在一切都是文件的假设下，我们可以不加区别的访问软盘，硬盘和闪存盘上的文件，但这里，因为中间多了一个网络协议，这种“一切都是文件”的方便特性就消失了。 因为这个需求很常见，很多 FTP 客户端，如 Windows 下的 LeapFTP, Mac 下的 Cyberduck，都做了一个贴心的功能，在你想要编辑远程文件的时候，自动将其下载成一个临时文件，等你修改结束后又自动上传。但是这依赖于 FTP 客户端，并不是每个客户端都提供了这类支持。</p>
<p>FTPFS 就是针对上面提出的这种不方便而出现的一种技术。通过利用一个叫做 FUSE (Filesystem in Userspace) 的技术，FTPFS 允许用户将远程的 FTP 文件系统挂载到本地的文件系统上，使得用户可以像操作本地文件一样操作远程文件1，包括查看，编辑，删除和重命名等等。而实际网络传输协议的细节，则对用户隐藏了。实际上，FUSE 技术可以用来实现很多“虚拟”的文件系统，而不仅限于将 FTP 文件系统挂载到本地文件系统上这一种。比如，使用 HTTP 协议的文件系统，SSH 服务器上的文件，Flicker 上的图片，维基百科上的文章，都可以通过 FUSE，抽象成一种虚拟的挂载在本地文件系统上的文件系统。这些虚拟的文件系统，隐藏了协议的细节，将各种不同类型的协议支持下的资源抽象成一个文件系统，也可以挂载到本地的文件系统上。</p>
<p>虚拟文件系统能把资源无差别的抽象成文件系统。这种做法消除了网络协议的不同造成的访问障碍，方便了用户对各种不同资源的访问。这种 “消除协议差异，一切资源都是文件”的思想，实际上来源于 Plan 9。毫不令人惊讶的是，在 Plan 9 中，干脆就没有 FTP 这个命令，所有对 FTP 的操作都是采用挂载 ftpfs 挂载文件系统的方式实现的。</p>
<p><strong>一切都是文件（这次是真的）</strong></p>
<p>上面我们提到了虚拟文件系统可以把资源无差别地抽象成一个文件系统，而这个思想是来源于 Plan 9 操作系统的。且慢，早在 UNIX Programming Environment 中， Brian W. Kernighan 就提出了 “UNIX 中，一切都是文件” 的设计哲学。事实上，UNIX 中的确很多对象是文件：进程是文件，设备是文件，命名管道也是文件。但是，也有很多不是文件，尤其是由其他非 Bell 实验室加入 UNIX 的组件。举例来说，计算机网络设备和服务不是文件（UNIX 的网络支持部分最先由 UC Berkerly 开发），图形界面中的对象也不是文件（UNIX 的图形界面支持最初由 MIT 的 X 工作组开发）。“一切都是文件” 这个口号因为 UNIX 的发展和新模块的加入而不再贴切。</p>
<p>UNIX 出现的时候，支持的设备都很简单，都可以抽象成文件交由内核统一管理，由内核提供 read/write 等系统调用访问设备。随着硬件的发展，一些新的硬件需要有超越系统调用范围的控制方式（例如我们可以控制光盘驱动器弹出托盘，而这个操作在传统磁盘驱动器上是不存在，也不能简单的抽象为 read/write 甚至 unmount 操作），或者为效率着想，需要用户空间程序直接和设备通信（如网卡，高速硬盘）。因为这些需求，和为未来扩展性考虑，Bell 实验室在 UNIX 第七版中，也不得不引入 ioctl 等具有无穷扩展性的系统调用机制，配合设备驱动程序，支持对设备的控制。这些做法，绕开了原先统一的 read/write 设备访问方式。也就是说，设备再也不能简单地抽象为文件了。</p>
<p>随着 UNIX 发展而失却的“一切都是文件”的纯粹哲学，正是 Plan 9 想要恢复的。在 Plan 9 中，通过实现一个叫做 9P 的文件协议，用户可以自由的把任何资源或服务抽象成本地的一个“虚拟的”文件或者目录，而对这些文件的操作，会通过 9P 协议，自动映射到对原来资源或者服务的操作。 这样，访问资源的各种细节就被隐藏了。在对付那些需要 ioctl 或者其他控制机制的设备或者应用程序时，Plan 9 提倡将程序的控制部分抽象成一个支持 read/write 的 ctl 文件，而非使用专门的 ioctl 系统调用。这样，其他程序就可以通过读写 ctl 文件与被控制的程序通信。从对资源和对控制的抽象不难看出来，Plan 9 把 UNIX 中“一切都是文件”的思想做了进一步的升华。在 Plan 9 里面，真的是一切都是文件了──设备是文件，窗口管理器是文件，Email 程序是文件（实际上所有程序都是文件），网络是文件（实际上所有服务包括 DNS 都是文件），等等。</p>
<h3>Plan 9 的创新</h3>
<p>要说 Plan 9 的特性，就不能不先介绍一下它的几个创造者。和 UNIX 一样， Plan 9也是从 Bell 实验室计算机科学研究中心开发的。其项目主要负责人是 Rob Pike （现在在 Google 工作，负责 Go 编程语言），当时在 Bell 实验室的很多人，包括 UNIX 的两位创始人，Ken Thompson 和 Dennis Ritchie ，以及 Brain Kernighan、Doug Mcllroy （UNIX 管道的提出者）都参与了这个项目的开发。从某种意义上来说，Plan 9 有点充当 UNIX 继承人的味道。事实上 Rob Pike 最初，也的确是想构建一个更加“现代的 UNIX”。除了坚持 UNIX 中已经成功了的“一切都是文件”，“KISS”等原则外，Plan 9 在原有 UNIX 的设计理念上做了新突破，其中最值得一提的，就是“分布式操作系统” 的理念。</p>
<p>Plan 9 这个分布式操作系统的出现和当时计算机发展的趋势是密不可分的。我们都知道， UNIX 是一种分时操作系统，用户分享机器资源。UNIX 操作系统则负责在各任务（或者说进程）之间调度。因此，UNIX 是一个中心化的操作系统。CPU、内存、IO 以及所有的任务的调度都是集中被 UNIX 管理的。上世纪 80 年代中期，更加便宜的微型计算机开始普及。这些微型计算机各自有着磁盘、CPU、内存和 IO 设备。Plan 9 的指导思想，就是把微机组织起来，方便的实现资源共享。</p>
<p>Plan 9 里，能共享的资源包括文件系统、图形界面、IO 设备、以及 CPU 和内存等计算资源。这些资源之间千差万别，我们固然可以针对每种资源设计一个协议，如文件分享用 NFS，图形界面用 X 协议，打印机用 CUPS 协议等等，不过这种做法在 Plan 9 的设计者看来是不够优雅的。他们采用的，是在上文我们已经提到过的“一切都是文件”的方法[cite:Plan 9, a distributed system]。我们可以用两个很有启发性的例子来说明。</p>
<p>例一、替换 CPU</p>
<p>假想一下我们有一台日常使用但性能不佳的笔记本，和一台不在本地但性能强劲的服务器。 我们当然能够使用远程计算机的强劲的 CPU 运行一些计算量特大的程序。这不是什么难事，因为几乎所有操作系统都支持登陆到远程的机器。然而，麻烦的是，如果在远程运行程序需要读写本地的文件，或者访问挂载在本地笔记本上的打印机，扬声器麦克风之类设备，我们除了在本地和远程之间把文件传来传去之外，并没有什么好方法。特别的，如果我们想借用另一台计算机上强劲的 CPU 做音频和视频解码，来播放一个放在本机光盘驱动器里的电影文件的话，我们是不可能指望远程计算机既能读本地的光驱，又能把音频投递到本机的扬声器上的。</p>
<p>Plan 9 中，有一个简单的 cpu 命令，能够让用户自然地使用一个其他机器上的 CPU 运行程序，且仍然能够访问本地的所有文件和设备。也就是说，我们可以用远程计算机上强劲的 CPU 做图像处理，媒体解码等任务，并且可以直接把声音播放到本地的扬声器。cpu 命令给人的感觉，是除了给机器换个了 cpu 外，其他一切都和原来一样。这个看似 “神奇” 的功能，其实在 Plan 9 里实现起来一点都不复杂： cpu 指令首先连接服务器上，然后将本地的所有资源和文件系统，包括窗口管理器，光盘驱动器，扬声器等设备（别忘了他们都是文件），一股脑儿挂载到服务器上，成为服务器上的资源。这样，在服务器上运行的程序，就可以“自然地”使用本地的键盘鼠标和显示器完成交互，还可以访问你本地的显示器扬声器等设备。</p>
<p>cpu 命令真的就是名副其实的换掉了本地计算机的 cpu （其实还有内存）而保留其他一切设备。Plan 9 的这个 cpu 命令，带有强烈的分布式操作系统的特征，而我们平时接触的操作系统都不是分布式操作系统，因此 cpu 这个命令至今在现代主流操作系统上没有完全等价物。</p>
<p>例二、进程间控制和通信</p>
<p>进程间通信可以提高使用计算机的效率（详细请参见 Page XX：开发人员为何应该使用 Mac OS X）。UNIX 下的管道就是一个经典的进程间通信的例子。在图形界面程序和集成化的程序出现后， 应用程序不断的把多种功能集成到一起，进程间通信反而变得相对困难了。比如说，即使有个给汉字加拼音的程序，除了来回复制粘贴，我们还是不能方便地从文字编辑程序中选取一段自动加上拼音。而 UNIX 下的编辑器可以借助管道很简单完成这样的操作。这个问题的本质困难，用操作系统的眼光来看，在于进程这个对象，没有在运行时暴露出应有的通信和控制接口。</p>
<p>Plan 9 的一切都是文件的思想从一个新的角度，解决了程序间的数据共享问题。Plan 9 倡导应用程序在运行时都把自己的内部状态抽象成一个文件系统。举例来说，一个邮件客户端程序不光支持图形界面下查看邮件，用户还能够直接通过</p>
<p>cat /mail/fs/inbox/1/subject<br />
cat /mail/fs/inbox/1/body</p>
<p>来查看收件箱（inbox）中第一封邮件的主题和内容。这种设计，使得应用程序不再成为进程间通信的障碍，从而拷贝粘贴也变得没有必要。比如说，我们可以直接把草稿箱里邮件的内容通过管道送给其他拼写检查器。邮件客户端提供的拼写检查器再差也没关系了。这种把应用程序中的对象暴露出来的想法，和 Mac OS X 中的应用程序暴露一个Applescript 字典的设计思想异曲同工。</p>
<p>同样的道理，Plan 9 也从一切都是文件的思想出发，解决了了程序控制问题。 Plan 9 鼓励每个应用程序和设备在抽象成文件的时候，都暴露出一个抽象的 ctl 文件。这样，其他应用程序可以通过向 ctl 文件写命令的方式，运行时控制应用程序。举例来说，Plan 9 的窗口管理器 Rio，提供了 ctl 文件，我们可以通过读取和写入 ctl 文件，实现一些 Rio 本身不支持的如平铺所有窗口的操作。同样，通过对邮件程序的 ctl 操作，我们可以实现邮件的发送和接受的控制等等。Mac OS X 下的 Applescript 也可以完成类似的功能。遗憾的是，除了 Plan 9 和 Mac OS X，其他操作系统对这类进程间控制和通信的支持都不够完整。</p>
<h3>实践者指南</h3>
</div>
<div>上面介绍的 Plan 9 特性，可以说仅仅是冰山一角 Plan 9 里还有很多其他创新，如 Rio 窗口管理器简化了窗口管理，fossil 文件系统使增量备份毫不费力，/proc 文件系统方便对系统的控制和支持远程 debug 等等。我们可以在桌面通信的 DBus 标准，Mac OS X 的 Time Machine, Linux 的 /proc 文件系统里找到这些技术的影子。因为 Plan 9 从来就没有大众化，有不少创新不为外人所知。</p>
<p>对 Plan 9 感兴趣，想要更多了解 Plan 9 的读者，可以到 http://plan9.bell-labs.com/plan9/ 下载 Live CD， 并在多台联网的机器或虚拟机中安装该系统。喜欢 Plan 9 里的一些命令，而不想折腾系统的读者，可以到 http://swtch.com/plan9port/ 下载可以在 Linux，Mac OS X 等主流操作系统上运行的 Plan 9 的移植程序。Rob Pike 的网站 cat-v.org 有最完整的 Plan 9 的资料，以及对 UNIX 设计哲学的反思。</p></div>
<div></div>
<div>Plan 9 中的很多思想，如/proc sysfs，还有 userspace 的虚拟文件系统，对 IPC 的重新处理如 DBus ，轻量级别的窗口管理器，UTF-8 等等，实际上都融入了 Linux 等现代操作系统。对于 Plan 9 这样含有众多优秀设计思想的操作系统，为什么最终没有流行开来，自然是很多人关心的。 ESR 曾经说，可能是因为当时 UNIX 已经足够好了。这样的解释很容易让人想起那句著名的 Worse is better。具体是什么原因呢，就交给各位读者思考了。</div>
<div></div>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2011/03/21/story-of-plan-9-revised/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>请给番外篇系列文章提意见</title>
		<link>http://blog.youxu.info/2011/01/06/to-readers/</link>
		<comments>http://blog.youxu.info/2011/01/06/to-readers/#comments</comments>
		<pubDate>Fri, 07 Jan 2011 04:51:11 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[CompSci]]></category>
		<category><![CDATA[pearl]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=1173</guid>
		<description><![CDATA[各位读者新年好.  今年, 博文准备将我博客上现有的的编程珠玑番外篇系列, 以及我将要写的该系列的一些文章结册出版. 这个系列能写到现在, 都要感谢各位读者的捧场和传播, 以及在留言中给... ]]></description>
			<content:encoded><![CDATA[<p>各位读者新年好.  今年, 博文准备将我博客上现有的的编程珠玑番外篇系列, 以及我将要写的该系列的一些文章结册出版. 这个系列能写到现在, 都要感谢各位读者的捧场和传播, 以及在留言中给出的那些意见和建议. 当然, 我继续需要各位读者更多的意见和建议.</p>
<p>在写作该系列的时候, 我心里大致有个写作的框架和轮廓. 在和博文沟通中, 郑晖老师, 博文视点的周老师和卢编辑, 还有其他业内高手等也都给过我不少的建议, 对我帮助非常大. 目前, 我依然在两个方面需要各位读者的意见. 具体来说:</p>
<p>第一, <strong>我希望知道读者的背景和对文章深度的期待</strong>. 在写这个系列的一开始, 我是以有趣的八卦的角度来写的, 文章的篇幅也不长, 用邓晖老师的话说, &#8220;致使有些问题无法充分展开&#8221;. 加上本人学识也有限, 写太深了我自己也驾驭不了, 写太浅了又会让人觉得是浮光掠影骗人钱财, 因此我想听听读者对这个系列的文章的深度和广度的期待. 同时,  我对读者的定位是: 觉得看完了一些编程的书还意犹未尽, 很好奇并觉得计算机科学和编程有趣的人 [按 <a href="http://en.wikipedia.org/wiki/Dreyfus_model_of_skill_acquisition">Dreyfus 分类</a> , 读者的水平应该是在新手(Novice)  之上, 高手(Expert) 之下的 Advanced Beginner 和 Competent 层次]. 因此, 我还想了解下读者的技术背景.  我把自己想象为一个看完了&#8221;编程珠玑&#8221;之后手不释卷, 感觉意犹未尽因此狗尾续貂的人, 因此定位是&#8221;以计算机科学和编程教科书之外的有趣, 有用的知识为主题的系列文章&#8221;。 您的意见将帮助我和出版社的编辑们处理书稿的时候, 在内容的深度和广度, 主线和八卦之间达到一种较好的平衡.</p>
<p>第二, 我<strong>希望有技术上的高手对我文章中无论大小的瑕疵提出猛烈的批评</strong>. 博客上的技术文章一旦要成书, 标准完全不可同日而语. 我也深切的感觉到自己在有些技术问题上的理解的不深刻. 质量达到 Knuth 大牛那样出书后给找到 Bug 的人发支票是不可能的, 但事先尽可能的找到 Bug 让书稿质量更加好还是值得追求的. 同时, 我会在书中致谢所有在成书过程中给与我帮助的人.</p>
<p>你可以在这里留言, 也可以在推特上 @<a href="http://twitter.com/mathena">mathena</a>.</p>
<p>蒙博文的卢编辑推荐, 本文在写作时参考了 vgod 的<a href="http://blog.vgod.tw/category/divine-code/">写书计划</a>.</p>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2011/01/06/to-readers/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>编程珠玑番外篇 -J. 高级语言是怎么来的-6</title>
		<link>http://blog.youxu.info/2010/07/12/scheme-1/</link>
		<comments>http://blog.youxu.info/2010/07/12/scheme-1/#comments</comments>
		<pubDate>Mon, 12 Jul 2010 22:51:52 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[CompSci]]></category>
		<category><![CDATA[pearl]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=1118</guid>
		<description><![CDATA[Scheme 语言是怎么来的 -1 导言 Scheme 是 LISP 的一个方言(dialect)。著名的 SICP 书就是以 Scheme 为教学语言（实际上 SICP 的作者就是 Scheme 的作者）。 虽然 Scheme 本身只是一个精简化的适合教学的语言... ]]></description>
			<content:encoded><![CDATA[<h4>Scheme 语言是怎么来的 -1</h4>
<p><strong>导言</strong></p>
<p>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 作为一个重要的历史分水岭，探究一下它的历史来源还是很有趣的。</p>
<p><strong>函数作为一级对象</strong></p>
<p>我们都知道 LISP 是一个函数式的编程语言。在 LISP 中，函数是一种基本类型。 类比的看，C 家族的语言中，整数是一个基本的类型，所以，整数类型的变量既可以作为参数传递给一个函数，也可以作为返回值返回。比如，两个整数求和这个函数，用 C 家族的语法就是</p>
<p><code>int add(int a, int b);<br />
</code><br />
因为在 LISP 里面，函数也成了基本类型。如果我们有一个 add 函数如下：<br />
<code><br />
(define (add x y) (+ x y))</code></p>
<p>显然，它在 LISP 里就和 C 里的 int 一样，能够作为参数传递给其他函数。</p>
<p>函数作为参数在 LISP 里非常普遍。 我们知道著名的 APPLY MAP 和 REDUCE 这三个“高阶”函数（所谓高阶的意义就是参数可以是函数）。其中 APPLY 的最基本形式可以带两个参数，第一个参数是函数，第二个参数是一个列表。APPLY 的效果就是把这个 列表 作为参数表，送给第一个参数所代表的函数求值。如果我们在 LISP 里面用 APPLY(add, (1, 2)) 结果就是3，即把 (1,2) 送给add 作为参数，结果自然是 3。 这是函数作为参数的例子，还有函数作为返回值的例子就不一一列举了。</p>
<p><strong>自由变量的幽灵</strong></p>
<p>在 add 这个函数的定义中我们可以看到，它的结果和两个输入值 x, y 有关。 如果我们用 add(1,2) 调用  add 函数，  我们至少期望变量 x 会被赋值为 1， 变量 y 被赋值为 2。而结果 (+ x y) 则相应的为 3。 在这个简单的例子中， 显然，如果 x 和 y 有一个值不知道的话， (+ x y) 的计算就无法完成。我们暂且把这些对函数求值不可缺少的变量称之为“必要变量”。显然，这些必要变量的值是需要确定的，否则函数无法进行求值。在我们 add 函数的例子里，x, y 这两个变量既是全部的必要变量，又是这个函数的参数，所以这个函数的结果就完全由输入的 x, y 的值确定。可以想象，任何一个像 add这样的所有的必要变量都是来自于输入参数的函数，不论在什么地方<br />
被调用，只要输入的参数值一样，输出的结果必然一样。</p>
<p>如果现实中所有的函数都有上面的性质的话，那就没有这篇文章了。可惜的是我们很快发现有好多函数不符合上面我们说的“输入的参数值一样，输出的结果必然一样”这个结论。我们甚至无须用 LISP 里面的例子来说明这一点。用 C 语言的都知道，取系统当前时间的函数 time，以及取随机数的函数 rand, 都是不需要输入值（0个输入参数）。因此任何时候这两个函数被调用的时候，我们都可以认为输入值一样（为 void 或 null)。但我们在不同的时间调用 time 或者多次调用 rand，很显然可以知道他们输出的结果不可能每次一样。</p>
<p>函数式编程里面有更多的类似的例子。这些例子说明了的确有些函数，对于同样的输入值，能够得到不同的结果。这就很显然的表明，这些函数的必要变量中，有些不是函数的输入参数或者内部变量。我们把这些变量，叫做自由变量(free variable) [相反的那些被成为受限变量(bounded variable)]。这里的自由和受限，都是相对函数讲的，以变量的取值是不是由函数本身决定来划分的。</p>
<p>虽然自由和受限变量是函数式语言里面的概念，但在命令式语言中也有影子。比方说，C 语言中，函数中用到的全局变量就是自由变量；在 Java 程序中，匿名内部类里面的方法可以用到所在外部类中的成员变量或者所在方法里标记为 final 的那些变量。这些可以被内部类的方法访问的，又不在内部类方法的参数表里面的变量都是自由变量。乐意翻看 GNU C Library 的好奇读者会看到，GNU libc 中的 rand 函数就用到了一个 random_data 的变量作为自由变量 (glibc/stdlib/random.c)。 time 也是一样，通过一个系统调用来设置时间，而这在原理上等价于用到一个叫做&#8221;当前时间&#8221;的自由变量 (glibc/stdlib/time/time.c)。</p>
<p>我们知道，在高级语言里面仅仅设计或者加入一个特性不难，难的是让所有的特性能协调一致的工作。比方说 Java 语言假设一切均为为对象，容器类型也假设装着对象，但是 int 类型却不是对象，让无数程序员为装箱拆箱大汗淋漓。 回到 LISP， 当函数允许自由变量，函数有能够被作为参数传来传去的时候，自由变量的幽灵就随着函数作为参数传递而在程序各处游荡。这就带来了两个问题，一个涉及到自由变量的值的确定机制，另一个涉及到这个机制的实现。</p>
<p><strong><br />
两种作用域</strong><br />
为了说明自由变量的幽灵和作用域，我们还是从一个例子入手。假设我们要一个做加 n 的函数。为了体现出自由变量，我们把它写成</p>
<p><code>(define (addn s) ( lambda x (+ x s)))<br />
</code></p>
<p>这个函数本身没什么特别的：输入一个 s, 输出一个 对任意 x 返回 x＋s 的函数。注意到这个函数的“返回值”是一个函数。 基于这个 addn 函数，我们可以定义 ＋1 函数 add1 函数如下，</p>
<p><code><br />
(define (add1 s) ((addn 1) s))</code></p>
<p>这个也很好解释，如果输入一个 s，<code> (addn 1)</code> 返回了一个加一函数，这个函数作用在 s 上，即可得到 s+1。一切看上去很顺利，直到我们用一个Scheme 出现前的 LISP 解析器去计算<code> (add1 4)</code>。 我们期望得到的值是 5， 而它给你的值可能是 8。怎么回事？</p>
<p>为了解释这个 8 的来源，我们可以模拟一下一个基于栈的解释器的工作过程。<code>(add1 4)</code> 调用首先将参数 s 赋值为 4 然后，展开 add1 函数，即将 s=4 压栈，计算 <code>(addn 1)</code>。在调用 addn 时。s 又作为了 addn 的形式参数。因此，按照基于栈的解释器的标准做法，我们在一个新的活动窗口中将 s =1 压栈。addn 这个函数返回的是一个 &#8220;<code>lambda x (+ x s)</code>&#8221; 的函数，其中 s 是自由变量。 然而一旦 addn 返回，栈中的 s=1 就会被弹出。当我们把这个返回了的 lambda 表达式作用到 4 上求值时候，x 是这个 lambda 表达式传入的形式参数，赋值为 4，栈里面的 s 的值 只有 s=4, 因此 (+ x s) 得到的是 8。</p>
<p>这显然不是我们想要的。总结这个结果错了的原因，是因为我们的解释器没有限定 lambda x (+ x s) 里面的自由变量 s 为 1。 而是在计算这个 lambda 表达式的时候才去查找这个自由变量的值。 自由变量的幽灵在函数上开了一个后门，而我们没有在我们想要的地方堵上它，让它在函数真正求值的时候泄漏出来。</p>
<p>我们不是第一个发现这个问题的人。 实际上， 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 中第一次引入和实现和闭包。</p>
<p>在编程语言的术语中，上面的让自由变量自由自在的在运行时赋值的机制，一般叫做动态作用域(dynamic scope)，而让函数和确定自由变量值在解析时静态绑定的机制，一般称之为静态作用域(static dynamic scope)。既然是静态绑定的环境是解析的时候确定的，而解析器是逐行解析程序的，所以，静态作用域的环境是完全由程序代码的结构确定的。因此有时候静态作用域又被等价的称为“文法作用域”(lexical scope)。上面我们的例子里。我们的真正意图是使用静态作用域，却遇到了一个使用动态作用域的 LISP 解析器，因此出现了 (add1 4) 等于 8 的错误。 但这个问题并不足以说明静态作用域一定好。动态作用域的问题，关键在于违反了 Alpha 变换原则和封装原则，不过不在此详细展开了。</p>
<p>后续的几小节提要</p>
<p><strong>著名的 FUNARG 问题</strong></p>
<p><strong><br />
Actor 计算模型的启示</strong></p>
<p><strong><br />
LISP 也面向对象?</strong></p>
<p><strong><br />
编译，编译，优化，优化</strong></p>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2010/07/12/scheme-1/feed/</wfw:commentRss>
		<slash:comments>23</slash:comments>
		</item>
		<item>
		<title>开发人员为何应该使用 Mac OS X 兼 OS X 小史</title>
		<link>http://blog.youxu.info/2010/02/28/why-mac-os-x-for-programmers/</link>
		<comments>http://blog.youxu.info/2010/02/28/why-mac-os-x-for-programmers/#comments</comments>
		<pubDate>Sun, 28 Feb 2010 05:03:57 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Cool Stuff]]></category>
		<category><![CDATA[MacOSX]]></category>
		<category><![CDATA[pearl]]></category>
		<category><![CDATA[tech]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=1057</guid>
		<description><![CDATA[一周前我和 Tinyfool 闲聊苹果操作系统，都认为对于开发人员来说，苹果操作系统（Mac OS）是上佳的选择。 Tinyfool 笔头很快，当即就写了一篇长文章， 我则笔头很慢，今天才全部码好。 他的文... ]]></description>
			<content:encoded><![CDATA[<p>一周前我和 Tinyfool 闲聊苹果操作系统，都认为对于开发人员来说，苹果操作系统（Mac OS）是上佳的选择。 Tinyfool 笔头很快，当即就写了<a href="http://tiny4.org/blog/2010/02/why-programmers-should-use-mac-os-x/">一篇长文章</a>， 我则笔头很慢，今天才全部码好。 他的文章的主要切入点在于 Mac 平台作为目标开发平台的优势，而我这篇的切入点主要是 Mac OS 作为一种开发工具的优势。</p>
<p><strong>开发人员的趁手工具</strong><br />
对于开发人员来说，所有的开发工具的最大的用途，就是最大限度的提高开发人员的生产率 (productivity) 和创造力(creativity)。在我们这个时代，使用 GUI (图形界面) 是一个提高生产率的好手段。虽然上一代的那些 UNIX 开发人员的确不需要 GUI。一个屏幕，一个键盘，一个编辑器，在陋巷，人不堪其忧，也不改其乐的黑客比比皆是， 但二十多年过去了， 现如今开发环境发生了巨大的变化。 比如说，相比较于当年程序员使用的基于文本的环境，在 GUI 下格式丰富的文档显得更直观，阅读体验更加好；就算工作中不需要开发任何 GUI 程序，现代开发人员也会使用 GUI 来完成网页图片和文档阅览等等。 因此，即使是最传统的用命令行的开发人员，其实也能沾 GUI 的光。 比如说现在最好的终端程序，都是 X 下模拟的，因为这些模拟的终端的出现，一些复杂的可视化功能可以在这些终端中实现了，比如 Unicode 的显示(rxvt-unicode)等等。</p>
<p>对于开发人员，拥有一组非常好用的，能够最大程度的提高生产率的开发工具乃是一大人生梦想。那么，这套开发工具从何而来呢？ 大体来说，这些工具来自于三个方面： 1. 通过系统和单一的应用软件提供的；2. 通过搭配使用各种应用软件 3. 通过定制和改变现有的应用软件。 这三点，对于 UNIX 开发人员是再熟悉不过的了， 无非就是写脚本，走管道而已。 所以，在前 GUI 时代，这一套哲学非常盛行， 开发人员都知道，需要通过安装脚本解析器，写一些的脚本，配置一些环境等等，才能把刚出厂的 UNIX 系统，改造成自己使用起来得心应手的系统。 基本上任何一个使用 UNIX/Linux 系统多年的人，机器里面都有各种各样的“私藏”的脚本。离开了这些脚本，他的效率会大打折扣。<br />
<strong><br />
GUI 时代传统的丧失</strong></p>
<p>上世纪 80年代的时候，GUI 时代和个人计算机普及的时代降临了。从此，计算机变成了个人电脑，历史上第一次，计算机不是专为开发人员设计，而是为了普通用户设计。普通用户的需求就是完成一个一个的现实问题，软件产业提供的解决办法就是为用户提供一个一个的应用软件，而不是让用户自己一行一行的编程和写脚本，巨大的软件需求瞬间成就了一个巨大的软件产业。 这样的一个间接后果就是，对于普通用户来说，让一台计算机变成能够帮助自己完成任务的“个人计算机”的唯一手段，就是叠床架屋的不断的装各种应用软件。</p>
<p>我们可以用一个简单的例子说明这种使用模式。 我们都知道，安装 Windows 系统的一个经验原则是把操作系统和应用程序分成两个逻辑盘，一个在 C 盘，一个在 D 盘。这个磁盘分区的经验原则不光网吧老板知道，连我大学里面只会点鼠标的那些女同学都知道。为什么有这个奇妙现象呢？其实，这是由 Windows 系统的用户的典型使用模式决定的。 在 Windows 系统上， 应用程序和文档是关键，操作系统只是一个随时可以重装的东西而已，所以干脆两者分开，互不影响。在这样的使用模式引导下，Windows 系统上格盘重装是非常低成本的，只要文档不丢，应用程序不丢就行。这种使用习惯，浪费了多少 geek 男美好的时光为人重装系统，又促成了多少美妙的姻缘 :)。 总之，在 GUI 时代，要解决一个问题，就装一个应用程序。至于应用程序之间的通信，和用非键盘鼠标的方法控制应用程序等等，都不再是要考虑的问题，有这样的需求的人成了非主流，非主流到以致于主流的操作系统和应用软件都不让你这么干了。 操作系统把所有其他的路都封死，就是明摆着告诉你，要想某样功能，请出门买软件。</p>
<p><strong>Smalltalk 的启示</strong></p>
<p><strong></strong><br />
其实 GUI 时代原本不应该是这样的。 我们都知道，GUI 原本是施乐的 Alan Kay 那一帮人做科研做出来的，Bill Gates 和 Steve Jobs 各自到施乐&#8221;抄袭&#8221; 了一部分过来，于是窗口啊按钮啊就到处都是了。 他们都看到了图形界面和面向对象的形， 看到了图形界面就是把按钮图标等等对象放好，然后鼠标点击拖动等等这些表面的东西。 因为所有的 GUI 界面都是从文字界面起步的，所以所有的 GUI 程序，其实就是原来的可执行程序的包装。 C++ 这个语言的出现也很讨巧，把 C 包装成了一个面向对象的语言，包装对包装， C++ 很讨巧的适应了把可执行程序 GUI 化的趋势， 成了 GUI 时代的主流开发语言。从表面上看，只要运行这些可执行的程序，就能够看到图形界面，就能够用鼠标点击操作他们，可是这些东西的底层，都是一个编译过了的可执行程序，原先 Smalltalk 中的那些运行时环境啊，对象容器啊，都统统不见了，所有的图形界面程序，还是直接运行在计算机的 CPU 上，而不是一个虚拟的面向对象的容器上。而这个面向对象的容器（也叫做“运行时”或者“运行环境”），才是 Smalltalk 的神。 简单的说，Smalltalk 本身具有一个面向对象的运行时，所以即使到了执行的时候，里面所有的对象还是可以互联互通的。 而 C++ 写出来的程序，除了编译之前是面向对象外，只要一编译，就全部变成机器码，和对象就再也没有任何关系了，也就不存在运行时去动态的查看(inspect) 和改变(modify) 这些程序对象的说法。 总之，因为历史的局限，这些 GUI 的平台，都是渐进的照猫画虎的演变的，所以没有一个平台像 Smalltalk 那样细致地考量过对象的互相通信的问题，再加上我们上面说了，反正扩展系统的方法就是引入新的应用软件而已，本身也没有互联互通的需求，所以这种抛弃运行时的，不让对象被外部程序控制的实现方法也无所谓不好。</p>
<p>可是开发人员不是普通用户啊，他们依然要改造计算机成为自己的工具的。在现有的现有工具不能解决问题的时候，要不然自己重新发明轮子，要不然就复用现有的一些工具，或者重新按自己的需求重新配置这些工具。 所以，和一般用户不一样，开发人员需要这些 GUI 的可配置性，也需要这些 GUI 程序之间的互联互通。 用黑话来说，第一个问题关系到 GUI 应用程序的脚本化， 第二个问题关系到 GUI 程序之间的进程间通信。 这两个问题，说起来简单，但都牵扯到 GUI 系统的根本设计问题。 历史在这里开了一个不大不小的玩笑，把这个唯一的机会给了 Mac OS X。其他操作系统，都因为这样那样的原因，在这两个问题上没有很好的解决方案。</p>
<p><strong>进程间通信，苹果的方案</strong></p>
<p>花开两朵，各表一只。我们先说 GUI 程序的进程间通讯的问题。 所谓的进程间通信 （IPC），就是两个程序之间的信息共享。 我们都知道，*nix 的一个强大之处就在于管道，管道是最简单，最廉价也是最常用的 *nix 进程间通信的方法。在 GUI 时代，最常用的 IPC 机制成了剪切板和鼠标拖放操作。这两个操作虽然都很直观，但都要人操作，离开了人，程序根本无法自动完成进程间通信。 而要工作效率的提高，就是要让计算机离开了人的干涉，也能完成这些任务。为了自动化这些任务，操作系统就不能简单的绘制窗口然后万事大吉了，它必须要知道哪些程序在运行，哪个运行的程序可以给哪个程序发消息通信等等，比如说，如果我们想自动的在阅读器里面选择一个词送给字典程序查释义，计算机就需要知道字典程序在运行的时候可以接受一个字符串，但是不可以接受图片。如果我们把字典程序抽象成一个可以提供“查字典”服务的对象的话，毫无疑问，如果想要向字典程序发送字符，必须首先知道字典程序能够接受什么，用什么方式把这个单词发送给字典等等。 所有的这些信息，都必须由操作系统托管才行<em>（不可能每个应用程序里面都要记着字典这个程序能接受字符串不能接受图片，这样每个应用程序都要记下所有其他可能的应用程序的信息，这是一个平方级别的关系，需要开发人员开发一个程序的时候还要兼顾其他所有程序，这显然是不现实的）</em>。用行话来说，必须要有一个统一管理的运行环境，来管理这些程序之间的互相通信问题。 我们上面说了，Smalltalk 的神在于一个统一的面向对象的运行时，使得所有的应用程序能互联互通。 可是所有平台上的 GUI 程序的演化进程都没有走这条路，而是只把外表给模仿走了；有的平台即使想做互联互通，也做得不彻底（比如微软的 OLE，COM 等等）。</p>
<p>是好东西，总会发光的。 但是要想让这个好东西被新的操作系统全盘采纳，要想让一个系统能够从底层到上层全部采用统一的运行环境，就要扔掉很多的历史包袱。甩掉这种历史包袱，对于任何操作系统都是不容易的。如果我们回到当年，一定会幻想，要是有个神人，能够不管市场也不管现有平台，从头打造一个没有任何历史包袱的干净整洁的 GUI 系统该多好。 历史就是这么戏剧，还真就安排了一个人，做成了这件事情，这个人，就是那个斯蒂夫乔布斯。</p>
<p>1985 年，乔布斯被苹果扫地出门，成立了 Next 公司， 一心想要做出质量上乘的 GUI 计算机系统。 历史给了乔布斯一个全部从头做的机会。这一次，乔老师和 Next 的开发人员意识到，光照搬 Smalltalk 的形是不行的，要连它的神也拿过来，重头设计进程间通信和 GUI 系统。 在内核层面，他们用了 Mach 这个为 BSD 设计的微内核。 这个操作系统内核就是为了替换已经过时的 UNIX 内核而设计的，其中的一个核心设计哲学就是重新设计进程间通信； 虽然现在基于微内核的操作系统已经不是什么潮流（为此 Linus 和 Tanenbaum 吵了一场著名的架)，但在相比较于当时 UNIX 系统的内核（此时 Linux 还没出现的，UNIX 内核只有 BSD， Bell, SUN 等几套），Mach 算是一个高的起点。在这个内核上，Next 公司的工程师开始构建面向对象的基础系统。 这套系统在 Smalltalk 中已经有了蓝图，因此这些工程师以 Smalltalk 为蓝图，先设计了一套基于 C 的语言，也就是 Objective C，照搬了 Smalltalk 的经典的 [对象 消息: 参数] 语法。<em> (我个人不喜欢 Objective C 这个语言，Smalltalk 是一种纯面向对象的动态类型的语言，Next 公司当年完全有机会用 Smalltalk 语言的，如果用了 Smalltalk，现在的 Cocoa 框架还会更加漂亮，代码更加干净；用 Objective C 这个自创的语言，不知道是不是因为专利的考虑，反正 Objective C 这20年的所有创新，就是在慢慢的更像 Smalltalk 而已，Java 和 Ruby 这几年也是不断的从 Smalltalk 拿东西）</em>。有了内核，有了语言，Next 构建了一个纯的面向对象的运行环境和类库（和 Java 和 .Net 的统一类库想法类似，只不过超前了十几年)， 这套类库，在当时叫做 NextStep, 所以所有的类名前面都带有 NS 前缀，无比丑陋。可惜的是，当年这个超越时代的类库太阳春白雪了，话说 Smalltalk 超越了时代 20年，所以90 年代中期的时候， 程序员才想起来当年 Smalltalk 的好，出现了 Java Ruby 等等受  Smalltalk 启发的语言。 乔老师虽然落后了 Smalltalk 5 年，却领先也业界 5-10 年，所以在 1995 年的时候， Windows 95 卖疯了， 乔老师的 NextStep 却没动静，只能把这个类库重新打包当成 Web 类库卖卖，即 WebObjects。这倒是无心插柳，生意不错，因为当时的 Web 开发已经吃尽了没有一个统一的运行环境的苦头（这也是日后 Java 风行的原因）。 我们说，是金子总要发光的，但是前提是要 (1) Next 再等几年，等业界回过神来认识到它的好处，(2) 获得一个主流的操作系统支持，把底层全换成乔老师的东西。 乔老师也知道这两个条件，所以加快了和 SUN 合作的步伐，想要把这套系统放到 SUN 的工作站上。 但是 SUN 本身有很强的底层技术，那段时间又狂推 Java, 所以其实乔老师在 SUN 这条路上胜算不大，况且 SUN 自己内核技术很强，所以肯定要肢解 NextStep 把内核重写，如果不和 SUN 玩，一来Next 这家公司能够多撑 5 年都是问题，二来几乎每家做个人计算机的公司都倒戈微软了，其他做工作站的公司又都有自己很强的底层技术，不可能用乔老师的玩意儿的，所以看起来乔老师和他的阳春白雪好像前景不妙。 可是天无绝人之路，放眼看当年的市场，只有一家公司没有倒戈微软，又没有很强的底层技术，又和乔老师有一些渊源，历史就是这么戏剧，这家公司就是把乔老师扫地出门的苹果。</p>
<p>90年代中期苹果的日子很不好过，个人电脑市场败给了 Wintel 联盟，新兴的市场上成绩也一塌糊涂，投资人也不糊涂，把当年让乔老师扫地出门的 Sculley 也扫地出门了，随后就把乔老师的公司给买了回来，让乔老师复职负责复兴苹果。 所以，上面我们说的两个条件就这样突然的满足了： 第一，他现在是老大了，所以可以彻底的把原来苹果的系统推倒重来，用自己的新家伙；第二，原来 Next 公司的那帮工程师不要担心失业了，现在由苹果负责发工资了，所以，正好可以让这些人着手改造苹果系统，主要的工作就是用自己带过来的新系统取代苹果的旧系统，并且让新系统的图形界面和旧系统保持风格的一致。 这个工作，从1995年 Next 被收购，到 2001 左右的时候才做好，这6年的时间里， 乔老师也顺带让苹果重新盈利了。</p>
<p>2001 年发布的 Mac OS X, 是苹果操作系统的第十代，完全基于了乔老师在 Next 开发出来的那套类库，所以自然的，具有了一个统一的面向对象的运行时。 这个运行时和类库系统，Mac OS X 把它叫做 Cocoa。其实 Mac OS X 刚出来的时候也不怎么好，不过依赖于这套设计精良的底层系统，Mac OS X 的迭代开发周期要比其他操作系统短多了 （仅慢于Linux, 不过 Linux 只有内核部分). 在短短的 8 年里，Mac OS X 就搞出了 7 次大的版本发布。 虽然我们看 Mac OS 好像从 10.0 到 10.6 只是次版本号在进步， 其实每次都是一个 major release, 大致相当于从 Window 95 到 Windows 98 或者 Windows 2000 到 Windows XP 这样级别的升级。 这样的发布却不改主版本号，一方面是从市场上考虑，另一方面也的确说明 OS X 的底层已经处于一个相对稳定的状态。 有很多 Windows 程序员非常推崇 .Net。 是的，.Net 的确是一个非常好的框架，可是想像一下，苹果在1995年的时候就有了一个统一的运行时，加上这么多年所有的程序都在这个统一的框架上开发，如果论在 Mac OS X 这个平台上的经验积累，应该说 Cocoa 社区是比 .Net 社区更加成熟的。</p>
<p><strong>应用程序脚本化</strong></p>
<p>光有进程间通信的系统还不能算是一个完全成熟的 GUI 系统，因为进程间通信依然是相对底层，而 GUI 上的应用软件是层出不穷的，不可能任何问题都跑到底层用进程间通信解决；所以，要想让 GUI 系统进化到易用和易于定制的水平，就需要开放对 GUI 程序的脚本控制。只有 GUI 程序能被外部控制了，才能真正的达到搭配使用 GUI 系统的效果。 其实，一旦有了一个统一的运行时，只要开发应用软件的时候统一设计一下脚本接口，用脚本控制 GUI 程序应该不难。 比如说，微软的 Office 系列套件， 就完全可以用 VBScript 去控制。 可惜的是，没有一个系统能够实现全系统的控制。 要实现全系统的控制，不仅仅要这个系统能够提供底层的支持，更重要的是要能说服所有的开发人员，或者说让所有的开发人员养成开放脚本接口的好习惯。 从技术上来说，这不是太大的问题，只要开发人员按照统一的脚本通信协议，实现特定的接口就行了，可是，如果一个平台上开发 GUI 的方法太多，开发人员只选自己喜欢的来，这种标准就不可能统一。 比如说 Linux 上 KDE 和 Gnome 都有自己的脚本化系统，可是开发人员有的用 KDE, 有的用 Gnome, 有的干脆两者都不用，这就谈不成有一致的接口。 一个平台要想有一致的脚本控制接口，除非 (1). 这个平台上就一种 GUI 开发方法，自古华山路一条，要不不做，做出来的东西就只能是标准的接口； (2). 这个平台上大部分的，主流的应用软件，都实现了这个脚本接口，这样因为这些程序的拉动，其他 GUI 程序想要融入这个平台上现有的应用软件的圈子相互通信，那也就必须要实现这个接口。 在 2000 年的时候，又只有一家公司能够同时满足这两个要求，就是苹果。 微软部分做到地了这两条，基本上用 VBA 统一了 Office 的控制，但是跳出 Office，微软的 OLE 对象模型几乎没有任何用武之地，与之捆绑密切的 VBA 自然无人问津。 不过据一些在金融行业工作的朋友说， VBA 能够大大提高 M$ Office 的生产率。</p>
<p>GUI 脚本化不是一夜之功，特别是我们说要做出统一的脚本接口，能兼顾各种程序的需求，这就完全不是一两年的时间能够搞定的，总需要很多年的技术积累和设计取舍后才能收敛到一个相对稳定成熟的系统， 而苹果，居然很神奇在十几年前就有这方面的经验，苹果再次怎么这么幸运呢？</p>
<p>在 80 年代后期的时候，苹果机上有一个非常超越时代的软件，叫做 Hypercard。 这个软件我曾经在上一代苹果上玩过，具体的思想就是你可以存储一张一张的“卡片”，这些卡片上面可以放置多媒体的声音，图像文字和其他对象，基本上就和现在网页一回事，唯一的区别就是这些卡片都存在本机。 在没有 Powerpoint 这类软件之前，这个 Hypercard 的软件可以用来做课件，做幻灯片演示等等，是个极其强大的工具。 为了让用户可以定制这个卡片，这个程序提供了一套非常强大的编程系统，叫做 Hypertalk。 因为这种编程语言是给普通人而不是程序员用的，所以你会感觉根本不是编程，而是写英语。这套东西，虽然本质上也是从 Smalltalk 学来的，但是用英语语法的方法编程的确是一个全新的思路，苹果把这个给普通人编程的语言发扬光大了，用更加贴近自然语言的方法重写了语言和文档，模仿 Hypertalk 系统，发布了一个跨系统的脚本控制语言，叫做 Applescript。这个语言就和自然语言没什么区别，比方说, 获取窗口的大小不再是<br />
<code>window.getSize()</code><br />
而是<br />
<code>get size of window</code></p>
<p>显示第 22 段的 第一个单词不再是<br />
<code>print(paragraph[22].getWordByIndex[0])</code><br />
而是<br />
<code>print the first word of paragraph 22</code></p>
<p>更狠的是，你还能用法语和日语写。 80年代后期的时候和整个 90年代初期，苹果基本上已经被 PC 机逼到墙角了，只剩下出版行业，设计行业等等专业的行业因为应用软件和图形处理能力的关系，依旧在守着苹果机。 两个行业的用户都需要自动化的 GUI 控制，但是编程都不怎么样，于是，这些应用软件的开发商也主动掺合加入 Applescript 旗下。 在90年代乔老师没有加入前，苹果自己把 Finder 全部脚本化，出版业的 QuarkXPress 和 Filemaker 也都完全脚本化，等乔老师入主苹果后，基于 Cocoa 的新技术，苹果一口气在 Mac OS X 上推出了 Safari, iTunes, iPhotos 等等软件，一股脑儿的全部脚本化了。 在别的公司都可望而不可求的历史机遇，又是被苹果给抓住了，一股脑儿全部塞进了 Mac OS X。这下，所有的第三方开发的工具，如 Firefox, Adium 这些，其实本来都不是苹果开发的，也没有太强的苹果渊源，但是 Firefox 要读 Safari 书签吧，哈，那就用 Applescript 吧，所以， Firefox 也逼着脚本化了（这个在其他平台上都不存在的事情）。 Adium 也是，这个聊天软件想要把 iTunes 正在播放的歌曲当成状态信息，好呀, Applescript，所以，也被带着脚本化了，而在 Linux 上的对应产品 pidgin 就没有这么脚本化。 所以，苹果平台已经成了一个惯性，你不想脚本化，就不带你玩，看你还脚本化不？</p>
<p><strong>结语</strong></p>
<p>我们都知道， UNIX 时代的主要哲学是提供给开发人员一组小巧精美且可以任意搭配使用的小工具，也就是所谓的 Software Tools, 然后任由开发人员由此出发，自己搭建自己的工具，打造自己的瑞士军刀。而开发人员所用的操作系统的目的，要不就是提供这样的一组开发工具，要不就是为这样的开发工具提供一个便利的平台，使得这样的工具变为可能。如果说 UNIX 是命令行时代的一个易于改造成 “自己的操作系统” 的操作系统的话， Mac OS X 就是 GUI 时代的这样的一个操作系统。 即使是从应用软件的层面看， Mac OS X 的底子好，更加容易出精品软件，所以即使仅使用应用软件，开发人员也应当优先考虑 Mac OS X。</p>
<p><strong>附A: 相对正确的 Mac OS X 使用习惯</strong></p>
<p>0. 一定要装 Quicksilver 或者用“服务”，否则就是把苹果当 Windows 用。<br />
1. 在苹果计算机上，因为有服务和 Quicksilver 这样的工具，90% 的程序间的拷贝粘帖都是可以避免的。<br />
2. 剩下的 10% 的程序内的拷贝粘帖，如果用一个好的编辑器的话，又可以省略掉 90%。</p>
<p><strong>附B： 为什么 Linux 系统在这个方面还不够好</strong></p>
<p>第一， Linux 上的 GUI 子系统，其实不是 Linux 的一部分，而是 X 和上面的 KDE 以及 Gnome<br />
等等。 这几年，这些系统终于开始统一管理一个面向对象的运行环境了。可是这两个系统都是用C++ 所写，所以免不了费很大的力气才有了运行时信息，绕了一个大弯路，如果一开始这两个系统就用 Smalltalk 之类的有运行时的语言编写，至少现在应该有能和 Cocoa 抗衡的框架。</p>
<p>第二， 这几年 X 也认识到了在脚本化控制上面的不足，所以几年前做桌面的 Redhat 提出了 DBus 标准。 可惜的是不是每个程序都开放了 Dbus 接口，所以和苹果比起来，还有比较长的路要走。</p>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2010/02/28/why-mac-os-x-for-programmers/feed/</wfw:commentRss>
		<slash:comments>73</slash:comments>
		</item>
		<item>
		<title>编程珠玑番外篇-I. 高级语言是怎么来的-5</title>
		<link>http://blog.youxu.info/2010/02/10/lisp-and-ai-2/</link>
		<comments>http://blog.youxu.info/2010/02/10/lisp-and-ai-2/#comments</comments>
		<pubDate>Thu, 11 Feb 2010 02:15:05 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[CompSci]]></category>
		<category><![CDATA[pearl]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=1052</guid>
		<description><![CDATA[LISP 语言是怎么来的–LISP 和 AI 的青梅竹马 B 上回我们说到 LISP 和 AI 很是青梅竹马的时候，浮光掠影地说因为 LISP 的基本数据单元&#8211;&#8221;链表&#8221;在知识表示上的比较优势。 我们说， AI 要... ]]></description>
			<content:encoded><![CDATA[<h4>LISP 语言是怎么来的–LISP 和 AI 的青梅竹马 B</h4>
<p>上回我们说到 LISP 和 AI 很是青梅竹马的时候，浮光掠影地说因为 LISP 的基本数据单元&#8211;&#8221;链表&#8221;在知识表示上的比较优势。 我们说， AI 要处理的数据结构和要刻画的现实世界的模型很复杂，使得数组等其他简单数据结构不能胜任，所以“链表”成了最佳的选择。 如果我们顺着这样的逻辑线往下看，似乎选择 LISP 这个“列表处理的语言”似乎是理所当然的。 可是，这个原因并不充分。 因为 LISP 语言可不仅仅是列表处理，还包括函数式编程等等其他。 反过来说，如果仅仅是列表处理对于 AI 至关重要的话，那么在 FORTRAN 和 Algol 这些通用编程语言又非常普及的传统语言里面写一些关于列表处理的函数岂不是更加直观和方便？ 归根结底，到底 LISP 还有什么其他奥妙呢？</p>
<p>当我们追寻函数式编程这条线索的时候，就会不可避免的触及到 AI 的早期历史。LISP 的特性，其实都是和当时 AI 的范式 (paradigm) 息息相关的。</p>
<p><strong>AI 范式的演变</strong></p>
<p>早在 McCarthy 这一代人提出 AI 之前，冯诺伊曼等人就开始研究什么是智能以及如何实现智能的问题了。 所不同的是，他们更加偏重于研究大脑的内部工作机理，并且试图通过在模拟大脑的工作机理，来实现智能。 这一学派的哲学很清晰： 人类大脑是一个标准的智能体，我们只需要让计算机模拟人的大脑的工作方式，计算机就有了和人类大脑一样的智能了。 对于这一派的研究者来说，他们相信大脑的结构和工作机理决定了智能，至于大脑是用脑细胞构成的，还是用电子电路模拟的，对于智能来说毫不重要。 这方面的著名工作就是冯诺伊曼的《计算机和大脑》这篇论文。 在这篇不算很学术的随笔里面，他分析了人的大脑有多少个神经元，计算机有多少个晶体管，通过这些定量的比较来解释计算机和人的大脑的差距。 当时和冯诺伊曼齐名的另一个神童是开创控制论的维纳。 他和冯诺伊曼一样，兼通很多学科。 和冯诺伊曼一样，他职业是数学家，但是也精通如神经科学和脑科学等学科。一个显然的例子就是在《控制论》这本书里面， 维纳对大脑和神经的分析比比皆是。这种对大脑和神经分析的传统，从 Cajal (对，就是写 <span class="citation book">Advice for a Young Investigator 的那个大神) </span>开始，一直延续到了后来 AI 中的联接主义(主要研究神经网络的一个人工智能学派)。</p>
<p>可是，从脑科学和认知科学的角度去分析智能在当时有一个非常大的局限: 脑神经解剖学本身不成熟。 比方说，现如今脑科学家在分析脑功能的时候一般会借助于 fMRI 和其他一些神经造影技术。这些技术可以做到实时观测到脑中血氧分布，直接确定大脑在执行特定任务时候的活跃部分。当年的科学家则只能使用有限的几种医学成像技术，或者从血管摄影的角度研究大脑。 受限于当时的研究条件，当年的研究者很难直接观测到脑神经的实时工作状态，分析大脑的实时工作机理。 因此，对脑的研究就很难做到非常深刻。 医学研究条件的限制，加上当时电子学的发展和集成度远远不够，用电子电路模拟大脑生成智能就显得非常遥远。 因此，虽然这一派的思想超前，但是大部分的工作都不在于真正的用电子电路模拟大脑，而是在探索脑科学和神经科学本身，或者仅仅用电子电路模拟一些简单的神经动力学行为和小规模神经网络。正是因为连接主义在实现人工智能本身方面进展并不大，所以在AI领域中一直不是潮流的研究方向。上个世纪 80 年代前成功实施的一些人工智能系统，极少是来自于连接主义学派的。直到80年代后随着 BP 算法的重新发现，联接主义才迎来了第二春。 这时候，LISP 已经过完 20 岁生日了。所以，联接主义 对 AI 领域使用的编程语言的选择的影响并不算大。</p>
<p><strong>符号主义</strong></p>
<p>虽然联接主义这一学派在当时不怎么流行，当年的 AI 研究可是进行的如火如荼。这如火如荼的学派，采用的是另外一套方法，我们称之为“符号主义学派”。 符号主义学派的渊源，可以直接追溯到图灵。图灵在人工智能方面做过很多的研究，其中最为大家所知的就是“图灵测试“了。 有句俗话叫做“在网上，没人知道你是一条狗”， 在这句话里，只要把“狗”换成“计算机”，就是简单版的图灵测试了。 用个比较“潮”的比方，图灵测试就是让一台计算机或者一个真实的人（又叫评委）在网上交流，然后让这个评委猜测和他交谈的究竟是人还是计算机。 如果这位评委也不能分辨谈话的对方到底是人还是计算机的话，我们就认为这个计算机已经足以“以假乱真”，拥有“和人类一样的智能”了，也就是通过“图灵测试了”。</p>
<p>在很长一段时间里，图灵测试一直是人工智能研究的圣杯(holy grail)。 也就是说，通过”图灵测试“ 成了人工智能研究的终极目标。 那么，自然的，最最直接的通过图灵测试的方法不是让计算机和人脑一样思考，而是只要能够让计算机处理对话中用到的的单词，句子和符号，并且在对话中能够和人一样的操纵这些单词和符号，计算机就有很大的希望通过图灵测试。 从最极端的情况来看，计算机甚至都不需要去“理解”这些句子的含义，都有可能通过图灵测试。 [具体细节可以阅读 Wikipedia 上的“<a href="http://en.wikipedia.org/wiki/Chinese_room">Chinese Room</a> (中文房间)”条目]。 有一个开源的聊天机器人，叫做 A.L.I.C.E.， 就把上面我们说的“只要能够处理和操纵符号，就有可能通过图灵测试”发挥到了近乎极致。 这个聊天机器人在图灵测试比赛中已经多次骗过人类评委，到了非常“智能”几乎能以假乱真的地步。可是，就是这样一个离通过图灵测试很近的机器人，其基本结构却简单到了我们都不能想像的地步：A.L.I.C.E.  的数据库里面有一条一条的规则，这些规则规定了她看到你说什么的时候她说什么。唯一有点“智能”的地方，就是有些规则不光取决于你这句话，还取决于你的上一句话。 [比如日常对话中我们先问“你喜欢看电影么？”，接着再问“什么类型的？”，这时候就需要前一句话推出这个问题是“（喜欢）什么类型的（电影)”]。“中文房间”的例子，和 A.L.I.C.E. 机器人如此简单的结构，都出人意料的显示出，即使计算机拥有了对符号的操作能力，通过了图灵测试，它也未必是是“有智能”的。 可惜这句话只是我的马后炮而已，在 AI 发展的早期，因为图灵测试的拉动，联接主义的相对弱势和符号主义的繁盛，都把全世界的 AI 研究往一个方向拽，这个方向，很自然的，就是“符号处理”。</p>
<p><strong>符号处理和 LISP 补充</strong></p>
<p>其实上一篇我们已经提到了，Alan Newell 和 Herbert Simon 认为对符号演算系统就可以衍生出智能，所以上面的文字，算是对符号主义这个 paradigm 做一个历史的小注解。 当我们把 LISP 放到这段历史中，就会自然的想到， 什么语言适合人工智能的问题，就变成了“什么语言能做符号处理”。这个问题的答案，读者也都猜到了，就是 LISP。</p>
<p>符号处理在 LISP 里面的长处前文我已经介绍过一些了，这里我们可以再补充几点零碎的。LISP 里有一个大家都知道的统一表示程序和数据的方法，叫做 S-Expression。 这个 S，其实就是 Symbolic 的意思。 把程序和数据都统一的当成符号，用现代编程语言的话说，就是 LISP 支持 meta-programming。LISP 程序可以处理，生成和修改 LISP 程序。这个特性，加上函数是一阶对象的特性，使得 LISP 远远比同时代的任何语言灵活。我本人不是 LISP 的用户（初级用户都算不上），因此在这一点上所知有限。但单就我有限的对 LISP 的理解，我认为 LISP 的这种灵活，恰好满足了基于符号处理的 AI 领域对语言的“强大的表达能力”（可以对任何复杂系统建模）和“高层的抽象能力” 的需求。关于第一点，有一个著名的段子，说任何一门编程语言技巧和思想被提出的时候，总会有一个高人出来，说，这个玩意儿在 LISP 里面早就有了，具体的例子包括刚才说的 metaprogramming, object oriented language。这里面蕴含的，就是 LISP 的强大的表达能力，使得很多编程的范式，在 LISP 里面都能实现，或者找到影子。 关于第二点，SICP 中例子比比皆是，讲得都比我深刻许多，就无需废话了。</p>
<p>在上篇文章中我提到，翻开任何一本编程的书，都会讲到“LISP是适合 AI 的编程语言”。那么，如果您和我当年一样，有兴趣从事 AI 方面的研究和探索，就不免要疑惑：“为了学习 AI， 我要不要学习 LISP” 呢？现在距离我当年的这个疑惑已经差不多8年了，我并没有一个确定的答案，但是我知道了更多的一些事实。</p>
<p><strong>如今的 AI 范式</strong></p>
<p>如果你让任何一个 AI 方向的研究者推荐几本适合初学者的书的话，十有八九他会提到 &#8220;Artificial Intelligence: A Modern Approach&#8221;(人工智能，一种现代方法) 和 &#8220;Artificial Intelligence: A New Synthesis&#8221; (人工智能，一个新的综述)。 这两本书的作者分别是 Peter Norvig 和 Nils Nilsson，都是 AI 领域的元老级人物。 如果你是一个对书名很敏感的人，你肯定会想：奇怪了，这种书又不是畅销书，难道这两位大牛写了书怕卖不出去，非要在书名上加一个 &#8220;现代&#8221; 或者 “新” 来吸引眼球？ 事实上，这个“现代”和这个“新”都大有来头。 实际上，这二十年来，AI 研究领域接连发生了好几个非常大的 paradigm shift. 传统的基于符号的 AI 方法不再是主流，取而代之的，是多种多样的基于统计的，基于自动推理的，基于机器学习的，基于群体智慧的，基于大规模数据集的等等各种各样研究方法的兴起。 这个 paradigm shift, 对于领域之外的人好像是静悄悄的，可实际上 AI 领域早已发生了翻天覆地的变化。所以才会有 “新” 和 “现代” 这样的词出现在书标题上。 不幸的是，大多写编程语言书的作者，未必全部知晓这个变化，因此还沿袭原来的框架，继续写下 “LISP是适合 AI 的编程语言” 这样一个早就不能完全反映现状的断言。 如果我们统计一个从事 AI 研究的研究者或者科学家用什么语言，答案可能是五花八门无所不有： 做 AI Search 的用 C/C++/Java, 做机器学习的如果模型和矩阵关系密切，可以用 Matlab, 如果统计计算较多，也可以用 R。 至于做数据挖掘等等，语言和库更加五花八门，根本无法宣称那一个语言占上风。LISP 是适合 AI 的语言的教科书神话，也早就被无数的这样的实例给打破了。</p>
<p>延伸阅读：</p>
<p><a href="http://stackoverflow.com/questions/130475/why-is-lisp-used-for-ai">http://stackoverflow.com/questions/130475/why-is-lisp-used-for-ai</a></p>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2010/02/10/lisp-and-ai-2/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>编程珠玑番外篇-H. 高级语言怎么来的-4</title>
		<link>http://blog.youxu.info/2009/08/31/lisp-and-ai-1/</link>
		<comments>http://blog.youxu.info/2009/08/31/lisp-and-ai-1/#comments</comments>
		<pubDate>Tue, 01 Sep 2009 00:46:16 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[CompSci]]></category>
		<category><![CDATA[pearl]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=1011</guid>
		<description><![CDATA[LISP 语言是怎么来的&#8211;LISP 和 AI 的青梅竹马 A LISP 语言的历史和一些番外的八卦和有趣的逸事，其实值得花一本书讲。 我打算用三篇文章扼要的介绍一下 LISP 的早期历史。 讲 LISP， 躲不过要... ]]></description>
			<content:encoded><![CDATA[<h4>LISP 语言是怎么来的&#8211;LISP 和 AI 的青梅竹马 A</h4>
<p>LISP 语言的历史和一些番外的八卦和有趣的逸事，其实值得花一本书讲。 我打算用三篇文章扼要的介绍一下 LISP 的早期历史。 讲 LISP， 躲不过要讲 AI （人工智能）的，所以干脆我就先八卦八卦他们的青梅竹马好了。</p>
<p>翻开任何一本介绍各种编程语言的书，都会毫无惊奇的发现，每每说到 LISP， 通常的话就是&#8221;LISP 是适合人工智能（AI）的语言&#8221;。 我不知道读者读到这句话的时候是怎么理解的，但是我刚上大学的时候，自以为懂了一点 LISP 和一点人工智能的时候， 猛然看到这句话， 打死我也不觉得&#8221;适合&#8221;。 即使后来我看了 SICP 很多遍， 也难以想象为什么它就 “适合” 了， 难道 LISP 真的能做 C 不能做的事情么？ 难道仅仅是因为 John McCarthy 这样的神人既是 AI 之父， 又是 LISP 之父， 所以 AI 和 LISP 兄妹两个就一定是很般配？ 计算机科学家又不是上帝，创造个亚当夏娃让他们没事很般配干啥？ 既然本是同根生这样的说法是不能让人信服的， 那么到底这句话的依据在哪里呢？ 我也是后来看 AI 文献， 看当年的人工智能的研究情况，再结合当年人工智能研究的指导思想， 当年的研究者可用的语言等历史背景，才完全理解“适合” 这两个自的。 所以，这篇既是八卦，也是我的心得笔记。我们一起穿越到 LISP 和 AI 的童年时代。 虽然他们现在看上去没什么紧密联系， 他们小时候真的是青梅竹马的亲密玩伴呢！</p>
<p>让机器拥有智能， 是人长久的梦想， 因为这样机器就可以聪明的替代人类完成一些任务。 二战中高速电子计算机的出现使得这个梦想更加近了一步。二战后，计算机也不被完全军用了，精英科学家也不要继续制造原子弹了，所以， 一下子既有资源也有大脑来研究 &#8220;智能机器&#8221;这种神奇的东西了。 我们可以随便举出当年研究繁盛的例子： 维纳在 1948 年发表了&lt;控制论&gt;, 副标题叫做 &lt;动物和机器的控制和通信&gt;,  其中讲了生物和机器的反馈，讲了脑的行为。 创立信息论的大师香农在 1949 年提出了可以下棋的机器，也就是面向特定领域的智能机器。同时，1949年， 加拿大著名的神经科学家 Donald Hebb 发表了“行为的组织”，开创了神经网络的研究；  图灵在 1950 年发表了著名的题为“计算的机器和智能”的文章，提出了著名的图灵测试。如此多的学科被创立，如此多的学科创始人在关心智能机器， 可见当时的确是这方面研究的黄金时期。</p>
<p>二战结束十年后， 也就是 1956 年， 研究智能机器的这些研究者， 都隐隐觉得自己研究的东西是一个新玩意，虽然和数学，生物，电子都有关系， 但和传统的数学，生物，电子或者脑科学都不一样， 因此，另立一个新招牌成了一个必然的趋势。John McCarthy 同学就趁着 1956 年的这个暑假， 在 Dortmouth 大学（当年也是美国计算机科学发展的圣地之一, 比如说， 它是 BASIC 语言发源地）， 和香农，Minsky 等其他人(这帮人当年还都是年轻人)，一起开了个会， 提出了一个很酷的词， 叫做 Artificial Intelligence， 算是人工智能这个学科正式成立。  因为 AI 是研究智能的机器， 学科一成立， 就必然有两个重要的问题要回答， 一是你怎么表示这个世界，二是计算机怎么能基于这个世界的知识得出智能。 第一点用行话说就是&#8221;知识表示&#8221;的模型, 第二点用行话说就是“智能的计算模型”。 别看这两个问题的不起眼， 就因为当时的研究者对两个看上去很细微的问题的回答， 直接造就了 LISP 和 AI 的一段情缘。</p>
<p>我们各表一支。 先说怎么表示知识的问题。 AI 研究和普通的编程不一样的地方在于， AI 的输入数据通常非常多样化，而且没有固定格式。 比如一道要求解的数学题，一段要翻译成中文的英文，一个待解的 sodoku 谜题，或者一个待识别的人脸图片。 所有的这些， 都需要先通过一个叫做“知识表示”的学科，表达成计算机能够处理的数据格式。自然，计算机科学家想用一种统一的数据格式表示需要处理多种多样的现实对象， 这样， 就自然的要求设计一个强大的，灵活的数据格式。 这个数据格式，就是链表。</p>
<p>这里我就不自量力的凭我有限的学识， 追寻一下为啥链表正好成为理想的数据结构的逻辑线。我想，读过 SICP 的读者应该对链表的灵活性深有感触。为了分析链表的长处，我们不妨把他和同时代的其他数据结构来做一比较。 如我在前面的一个系列所说，当时的数据结构很有限，所以我们不妨比较一下链表和同时代的另一个最广泛使用的数据结构-数组-的优劣。 我们都知道，数组和链表都是线性数据结构，两者各有千秋，而 FORTRAN 基本上是围绕数组建立的，LISP 则是围绕链表实现的。通过研究下棋，几何题等 AI 问题的表示，我们的读者不难发现， AI 研究关心于符号和逻辑计算远大于数值计算，比如下棋，就很难抽象成一个基于纯数字的计算问题。 这样，只能存数字的数组就显得不适合。 当然我们可以把数组扩展一下，让这些数组元素也可以存符号。不过即使这样，数组也不能做到存储不同结构的数据。 比方说棋类中，车马炮各有各自的规则，存储这些规则需要的结构和单元大小都不一样，所以我们需要一个存储异构数据单元的模块，而不是让每个单元格的结构一样。 加上在AI 中，一些数据需要随时增加和修改的。 比如国际象棋里，兵第一步能走两步，到底部又能变成皇后等等，这就需要兵的规则能够随时修改，增加，删除和改变。 其他问题也有类似的要求，所有的这些，都需要放开数组维度大小一样的约束，允许动态增加和减少某一维度的大小，或者动态高效的增加删除数组元素。 而一旦放开了单元格要同构和能随时增加和删除这样两个约束，数组其实就不再是数组了，因为随机访问的特性基本上就丢失了，数组就自然的变成了链表，要用链表的实现。</p>
<p>所以，用链表而不是数组来作为人工智能的统一的数据结构，固然有天才的灵机一动，也有现实需求的影响。当然，值得一提的是，在 Common LISP 这样一个更加面向实践而不是科学研究是 LISP 版本中，数组又作为链表的补充，成了基本的数据结构，而 Common LISP，也就能做图像处理等和矩阵打交道的事情。这个事实更加说明，用什么样的数据结构作为基本单元，都是由现实需求推动的。</p>
<p>当然，科学家光证明了列表能表示这些现实世界的问题还不够， 还要能证明或者验证额外的两点才行， 第一点是列表表示能够充分的表示所有的人工智能问题，即列表结构的充分性。 只有证明了这一点，我们才敢放心大胆的用链表，而不会担心突然跳出一个问题链表表达不了；第二是人工智能的确能够通过对列表的某种处理方法获得，而不会担心突然跳出一个人工智能问题，用现有的对链表的处理方法根本没法实现。只有这两个问题的回答是肯定的时候，列表处理才会成为人工智能的一部分。</p>
<p>对于这两个问题，其实都并没有一个确定的答案，而只是科学家的猜想，或者说是一种大家普遍接受的研究范式（paradigm）。 在 1976 年， 当年构想 IPL， 也就是 LISP 前身的两位神人 Alan Newell 和 Herbert Simon ，终于以回忆历史的方式写了一篇文章。 在这篇文章中，他们哲学般的把当时的这个范式概括为： 一个物理符号系统对于一般智能行为是既充分又必要的（ A physical symbol system has the necessary and sufficient means for general intelligence action）。 用大白话说就是，“智能必须依赖于某种符号演算系统，且基于符号演算系统也能够衍生出智能”。 在实践中，如果你承认这个猜想，或者说这个范式，那你就承认了可以用符号演算来实现 AI。 于是，这个猜想就让当时几乎所有的研究者，把宝押在了实现一个通用的符号演算系统上，因为假如我们制造出一个通用的基于符号演算的系统，我们就能用这个系统实现智能。</p>
<p>上面我们说过， 链表的强大的表达能力对于这个符号演算系统来讲是绰绰有余的了，所以我们只要关心如何实现符号演算，因为假如上面的猜想是对的，且链表已经能够表示所有的符号， 那么我们的全部问题就变成了如何去构建这样的符号演算系统。后面我们可以看到， LISP 通过函数式编程来完成了这些演算规则的构建。</p>
<p>这里，需要提请读者注意的是， LISP 的全称是 LISt Processing， 即列表处理，但实际上 LISP 是由两种互相正交的哲学组合形成的， 一个是列表处理，另一个是函数式编程。 虽然在下面以后，我们会介绍 S-Expression 这样美妙的把两者无缝结合在一起的形式，但是为了清晰我们的概念，我要强调一下列表处理和函数式编程是两个正交的部分。实际上，我们完全可以用其他的不是函数的方式构建一个列表处理语言。在历史上，早在 FORTRAN 出现之前，Alan Newell 和 Herbert Simon 就用汇编实现了一个叫 IPL 的语言，而这个 IPL 语言就是面向过程的对列表处理的，而后，McCarthy 一开始也是用一系列的 FORTRAN 子程序来做列表处理的。比如 LISP 里面的 CAR 操作，其全成实际上是 Content of the Address portion of the Register， 顾名思义，寄存器的地址单元内容，也即列表的第一个元素（和C表达数组的方式类似，这里寄存器中存着指向列表第一个元素的指针）。 函数式的却不以列表为基本数据单元的语言也很多，比如 Scala ，就是以对象为基本数据单元。 因此，函数式和列表处理是不一定要互相耦合的。 那么，到底是什么原因使得 LISP 选择函数式，这样的选择又为啥更加适合当时 AI 的研究呢， 我们下节将继续介绍当时 AI 的研究范式，强弱 AI 之间的辩论和函数式编程在当年 AI 研究上的优点。</p>
<p>（待续）</p>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2009/08/31/lisp-and-ai-1/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>编程珠玑番外篇-G. 程序员心底的小声音</title>
		<link>http://blog.youxu.info/2009/08/19/astill-small-voice-for-programmers/</link>
		<comments>http://blog.youxu.info/2009/08/19/astill-small-voice-for-programmers/#comments</comments>
		<pubDate>Thu, 20 Aug 2009 00:19:23 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[pearl]]></category>
		<category><![CDATA[reading]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=1005</guid>
		<description><![CDATA[(&#8220;高级语言怎么来的“ 系列仍然有后续，这篇是临时插入) 程序员心底的小声音 编程大约有三个境界，新手，高手，和高不成低不就的中手。这三个境界，大致和王国维先生划定的做学问... ]]></description>
			<content:encoded><![CDATA[<p>(&#8220;高级语言怎么来的“ 系列仍然有后续，这篇是临时插入)</p>
<h4>程序员心底的小声音</h4>
<p>编程大约有三个境界，新手，高手，和高不成低不就的中手。这三个境界，大致和王国维先生划定的做学问的三个境界一一对应。 一般来说，如果不经过几十万行的代码的锤炼（衣带渐宽终不悔，为伊消得人憔悴），或者长期在一个高手团队里面打磨切磋，那么无论怎么样的理论熟悉，打字熟练，考试全A，编程起来，都应该算是中手。一个中手如果机缘很好，得到高人亲自指点，则能很快成长为高手，如果没有这样的机缘，那就要在“众里寻她千百度”这个层次苦苦的求索锤炼很久，才能“蓦然回首”。</p>
<p>读书是一种很好弥补没有高手在场的方法，都说书是最好的老师嘛。 可是现实是，高手写给中手的书很少。 在任何行业，适合新手的入门的书很多，适合中手的书就很少。 原因有两个，一来高手极少愿意耐心的的指点成长秘诀，就算写了，也是蜻蜓点水，因为这些经验啊结论啊，都被他们本身提炼成了珠玑，他们觉得最重要的也就是这么寥寥几句，也没有太多的废话好写。 而读者如果没有类似的经历，则看到这些珠玑，只是觉得把玩颇为有趣而已，极少能有同感。 鲜有高手，能把技术书写成散文集，如 Brooks 一样，在《人月神话》中把经验教训和经历背景等一一道来，并且从这些经历中抽出一般性的知识。 所以，高手的风格一般是浮光掠影概括一下大致自己领会到的几个原则和教训。 这些寥寥数语的珠玑，对于其他高手来说一看就懂，但是对于中手来说就很难以理解。 所以很多高手写出来的给中手看的书就曲高和寡。 二来，中手其实水平差异巨大，偏好也各不一样，有的或许根本认识不到自己应该走的成长轨迹，有的认为这些书籍是片面知识，所以把不喜欢的书都给扔垃圾堆了，光捡自己喜欢的书看；有的未必看得上高手的经验，认为高手说的那些自己也早就领悟到了。所以，也不喜欢购买这些书籍。这两个原因，就造成了高手提携中手的书在市场上很少见到。</p>
<p>不过这样的书倒不是没有，比方说在编程领域， 我至少可以推荐四本这类的书，这四本分别是<br />
《Pragmatic Programmer》, 《The Art of UNIX Programming》, 《Elements of Programming Style》 和 《The Productive Programmer》. 这四本书，都是高手所写，都属于高手指导中手的典范。第二第三本我原先都介绍过。 而第四本<a href="http://www.luanxiang.org/blog/archives/593.html">余晟同学的书评</a>比我写得好几百倍，所以我就以 《Pragmatic Programmer》 为例说明这个问题。</p>
<p>我们前面说了，对于中手，特别是在“寻她千百度”这个层次的中手来说，或许本身已经捡到了一些珠玑，或许对于像 《Pragmatic Programmer》 里面说的那些 Tip，有的是深有同感的。 比如 DRY (Don&#8217;t Repeat Yourself 不要重复你自己)， 基本上大家都知道，可是在实际中（至少我自己）还是不停的一次一次的犯错误，做事情不符合 DRY 原则（一次一次犯这个错误本身也是一个 DRY 错误， 因为 DRY 原则要求你对于每种错误你只能犯一次）。 读到的时候深有同感， 写代码的时候却忘到 Java 国去了，这还真不是个案，是非常普遍的现象。</p>
<p>能不能让正确的原则指挥正确的行动本身，其实就是区分是否是高手的一个显著标志。 试想，两个都了解 KISS 原则的程序员在一起写代码，高手的代码必然是自然流露出 KISS 的优雅，而中手或许需要旁人提醒和多次重构，才能达到理想的状态。 出现这个问题的原因很明显&#8211;中手没有完全内化 KISS 原则，所以尚且不能“运用自如”。 内化是一个非常复杂的认知过程，本身涉及到大脑中 mind set 和 paradigm 的切换， 所以必然不是一个简单的隔夜就能完成的过程，这也就是为啥能够“消得人憔悴”，但是切换一旦完成，实践中就会自然流露出这种新的认识，也就是到了一个新的境界，发现灯火阑珊处。</p>
<p>那么原则和知识的内化这个过程怎么能够加速呢？也就是说，怎么较快的到达高手境界呢？ 可以肯定的说，光靠对自己说我“下次一定按照这个原则这样做”是不行的。认知科学认为，频繁的高强度的外部刺激和自主的有意识的反复提醒是加速内化的两个重要方法。 第一个方法需要外部环境的支撑。 试想，如果一个程序员不是天天和复杂文本处理打交道，他必然没有足够外部刺激来熟悉和内化正则表达式； 如果一个程序员不是天天和极度复杂的大项目打交道，用全自动编译环境和自动单元测试也显得无甚必要，所以，除非你正好掉进了一个天天有高强度训练的环境，否则全靠第一点是不可能的。 尤其是自学一门语言和一门技术的程序员，往往在没有高强度训练之前就拿着这些技能投入工作了，因此想成为某方面的高手，只能采取第二条路，就是有意识的强化实践和反复提醒。</p>
<p>《圣经》里有个故事，说一个人在沙漠里，信心丧失的时候，突然听到 “A Still Small Voice” (平静的小声音), 即上帝的启示。这个平静的小声音把他从绝望中拉了回来。 其实对于这个人来说，他本身的实践能力在 “平静的小声音” 出现前后并没有多大的改变，唯一的不同就是他知道该怎么做了。</p>
<p>内化一个知识或者认识的时候所循的路径也是一样的。 我们常常会“忘了”应该怎么正确的做一件事情（这个地方的“忘了”，指我们之前从书中或者其他渠道读到看到了正确的原则或方法，但是在那一刻脑子里压根没考虑这个原则或方法，因为这个原则或方法压根没有亲自实践过，所以根本不是自己的一部分，不属于自己）。 在这个时候， 如果突然有一个平静的小声音跳出来，说，“嘿，你是不是该遵循这个原则，用这个方法？” 无需说，我们对问题的思考就能顿时全面起来， 也会更加深刻的理解原先读到看到的不属于自己的原则和方法。当然，我们更加感兴趣的是，如何能够在身边没有高手和上帝发出这样的平静的小声音的时候，自己发出这样的小声音？</p>
<p>怎么靠自己呢，记得鲁迅小朋友破坏公物在课桌上刻的“早”么？是的，我们需要抽象出一些简单的词句和规则，靠记忆和不断的提醒，小规模的内化这些小声音，让这些简单的小声音能够时刻从大脑里跳到耳边，提醒自己。 具体来说，在阅读上面的几本书，尤其是阅读 《Pragmatic Programmer》 的时候，如果仅仅是以普通的浏览的方式阅读，就会很简单的陷入 “啊，这个我知道了，啊，那个我了解了，恩，这个以后要注意” 的套路中。 这样的阅读方式，只会强化原有的自己已经知道的部分，而不大可能把“以后要注意” 这部分全部内化。所以，自负的读者读完了之后必然觉得“哈哈，高手不过如此，大部分我也知道嘛”，而不是“是的，我还有不少要注意”。 这两个态度，就把高手和易于满足的中手永恒的隔开了。 我觉得，想要内化这些小声音，还是要靠实践，如果不实践，即使你把这些小声音写在 100 块钱的高档笔记本上也没有用。我个人觉得，理想的阅读状态应该是先大致理解和记住里面的 Tip, 然后每周争取实践 2-3 个 Tip。其实如此做完一圈也就是半年，在这一圈之后就会记住所有的 Tip 的内容，这时候，小声音就成了自己的一部分了。然后在剩下的几年里，只要时时有这些小声音挑出来，告诉你，“要自动频繁的测试”，或者“别手动做繁琐的工作”，你会很快的被强迫转换到高效而优雅的工作状态。 到了那个时候，这些小声音就再也不会跳出来了，因为你早就自然的遵守这些小声音的要求了。</p>
<p>《Pragmatic Programmer》 和 《The Elements of Programming Style》 这些书里面的 Tip 都不是来自上帝的话语，却都是值得随声带着的小声音。 其实只要是处理过实际问题，编过几万行程序，大多程序员都差不多都会有或深刻或浅显的对各个 Tip 都感悟，而且我相信或许对有些 Tip 的认识能比原书的作者还要深刻，这是很正常的。 事实上每一个 Tip 只是一句话而已，对这一句话的理解层次， 则完全不这一句话能够覆盖的。 比如说，一天写了两个 Hello Word 的程序员也会领悟到 DRY, 一个刚刚重构扔掉掉几千行重复代码的程序员也领悟到 DRY， 而这两个 DRY 所在的认识层面， 必然是不一样的。 再好比说我在“编程珠玑番外篇”这个系列里面写的有些文字，看上去很有道理，但我本人对这些文字的认识可能比我的读者要浅， 但是这倒不妨碍引发读者思考。 即使有些牛人觉得上面这几本书的作者在某些原则上的认识不够深刻，或者觉得作者只是在罗列一些小碎片，读这些书，特别是 《Pragmatic Programmer》 这本书的那些小 Tip，依然是有益的， 因为他或许能触发你高于作者的思考，然后在你脑中形成更加圆润的珠玑。而对于像我这样属于中手下游平时又没有大项目训练的人，《Pragmatic Programmer》 这本书，和其他的几本书一起， 实在是很好的“小声音汇编”。</p>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2009/08/19/astill-small-voice-for-programmers/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
		<item>
		<title>编程珠玑番外篇-F. 高级语言怎么来的-3</title>
		<link>http://blog.youxu.info/2009/07/02/fortran/</link>
		<comments>http://blog.youxu.info/2009/07/02/fortran/#comments</comments>
		<pubDate>Fri, 03 Jul 2009 04:16:37 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[CompSci]]></category>
		<category><![CDATA[pearl]]></category>
		<category><![CDATA[tech]]></category>
		<category><![CDATA[fortran]]></category>

		<guid isPermaLink="false">http://blog.youxu.info/?p=950</guid>
		<description><![CDATA[FORTRAN 语言是怎么来的 在高级语言是怎么来的子系列的第一篇中， 我们结合当时硬件的特点，分析了 FORTRAN 为什么一开始不支持递归。但是 FORTRAN 本身是怎么来的这个问题其实还是没有得到正... ]]></description>
			<content:encoded><![CDATA[<h3>FORTRAN 语言是怎么来的</h3>
<p>在高级语言是怎么来的子系列的<a href="http://blog.youxu.info/2009/05/13/hpl/">第一篇</a>中， 我们结合当时硬件的特点，分析了 FORTRAN 为什么一开始不支持递归。但是 FORTRAN 本身是怎么来的这个问题其实还是没有得到正面回答，本节我们就谈谈 FORTRAN 语言本身是怎么来的。</p>
<p>其实，FORTRAN 语言也是现实驱动的。 所以我们还是回到当时，看看当时程序员的需求和软硬件条件，看看 FORTRAN 是怎么来的。 了解历史的另一个好处是， 因为 FORTRAN 的发展历史正好和高级语言的发展历史高度重合，所以了解 FORTRAN 的背景，对于理解其他高级语言的产生都是大有帮助的。</p>
<p><strong>1. 困难的浮点计算</strong><br />
我们先从硬件的角度说起。 大致从 1946 年第一台计算机诞生，到 1953 年，计算机一直都缺少两件非常重要的功能，一个叫浮点计算，一个叫数组下标寻址，这两个功能的缺失直接导致了高级语言的兴起。 我们依次单个分析。读者对浮点计算应该都不陌生，用通俗的话说就是如 0.98×12.6 这样的实数乘法，或者  0.98 + 12.6 这样的实数加法的运算。用行话说，就是用计算机进行大范围高精度数的算术运算。</p>
<p>学过二进制的同学都知道，二进制整数之间的乘法和加法等运算都是相对简单的，和正常的十进制运算是一样的，只是把加法和乘法这些基本操作用更加简单的逻辑或(OR) 和 逻辑与 (AND) 实现而已，在电子电路上也很好实现。 因此，就是世界上最早的电子计算机，ENIAC，也是支持整数的乘法加法等算术操作的。</p>
<p>可是浮点运算就不一样了。 因为一个额外的小数点的引入，在任何时候都要注意小数点的对齐。 如果用定点计数，则计数的范围受到限制，不能表示非常大或者非常小的数。所以，浮点数一般都是用科学记数法表示的，比如 IEEE 754 标准。（不熟悉 IEEE 754 的读者也可以想像一下如何设计一套高效的存储和操作浮点数的规范和标准，以及浮点算法）， 科学记数法表示的浮点数的加减法每次都要对齐小数点，乘除法为了保持精度，在设计算法上也有很多技巧，所以说，相比较于整数的运算和逻辑运算，浮点运算是一件复杂的事情。落实到硬件上就是说，在硬件上设计一个浮点运算，需要复杂的电路和大量的电子元器件。在早期电子管计算机中，是很少能做到这么大的集成度的。因此，不支持浮点也是自然的设计取舍。在计算机上放一个浮点模块这个想法，需要等电子工业继续发展，使得电子管体积小一点，功耗低一点后，才能进入实践。</p>
<p><strong>2. 关于浮点计算的一些八卦</strong></p>
<p>关于浮点，这里顺带八卦一点浮点计算的事情。在计算机芯片设计中，浮点计算一直是一个让硬件工程师头疼的事情，即使到了386时代，386 处理器 (CPU)的浮点乘法也是用软件模拟的，如果想用硬件做浮点乘法，需要额外购买一块 80387 浮点协处理器 FPU，否则就在 386 上做软件的模拟。这样做的原因在一块硅片上刻蚀一个 CPU 和一个FPU 需要的集成度还是太高，当时的工艺根本没法实现。真的把 FPU 和 CPU 融在一起刻蚀到一块硅片上，已经是 1989 年的事情了。当时，Intel 把融合了 80386 和 80387 的芯片改了改，起了个名字叫 80486，推向了市场。带着浮点的处理器的普及，使得个人计算机能做的事情变多了。极度依赖于浮点计算的多媒体计算机（视频和声音等多媒体的压缩，转换和回放都是要依赖于浮点运算的），也正好随着 80486 的流行，逐渐普及开来。</p>
<p>在处理器上融合浮点运算依然是困难的。即使到今天，很多低端的处理器，都不带有浮点处理器。 所以，号称能够上天入地的，被移植到很多低端设备比如手机上的 Linux 内核，必然是不能支持浮点运算的，因为这样会破坏内核的可移植性。我们都知道， 在内核模式下，为了保证内核操作的原子性，一般在内核从事关键任务的时候所有中断是要被屏蔽的，用通俗的话说就是内核在做事情的时候，其他任何人不得打 扰。 如果内核支持浮点运算，不管是硬件实现也好，软件模拟也罢， 如果允许在内核中进行像浮点计算这样复杂而耗时的操作，整个系统的性能和实时响应能力会急剧下降。  即使是在硬件上实现的浮点运算，也不是件容易的事情，会耗费CPU较多的时钟周期，比如 Pentium 上的浮点数除法，需要耗费 39 个时钟周期才行，在流水线设计的CPU中，这种占用多个时钟周期的浮点运算会让整个流水线暂停，让CPU的吞吐量下降。在现代 CPU 设计中，工程师们发明了超标量，乱序执行，SIMD 等多种方式来克服流水线被浮点运算这种长周期指令堵塞的问题，这都是后话了。</p>
<p>正因为对于计算机来说，浮点运算是一个挑战性的操作，但又是做科学计算所需要的基本操作，所以浮点计算能力就成了计算机能力的一个测试标准。我们常常听说有一个世界上前 500 台最快的超级计算机列表，这里所谓的“快”的衡量标准，就是以每秒钟进行多少次浮点计算(FLOPS) 为准。按照 <a href="http://Top500.org">Top500.org</a>, 即评选世界上前 500 台超级计算机的机构 2009年6月的数据，世界上最快的计算机，部署在美国能源部位于新墨西哥的洛斯阿拉莫斯国家实验室 (Los Alamos National Laboratory)，当年造出第一颗原子弹的实验室。这台超级计算机，浮点计算速度的峰值高达 1456 TFlops，主要用来模拟核试验。因为美国的所有核弹头，海军核动力航母中的反应堆以及核试验，都由能源部国家核安全署(NNSA) 管理，所以能源部一直在投资用超级计算机进行核试验。 在 1996 年美国宣布不再进行大规模的物理核试验后的这么多年，美国能源部一直用超级计算机来做核试验，所以在 Top500 列表中，美国能源部拥有最多数量的超级计算机。</p>
<p><strong>3. 数组下标寻址之障</strong></p>
<p>言归正传，我们刚才说了在早期计算机发展史上，浮点计算的困难。除了浮点计算，还有一件事情特别困难，叫做数组下标寻址。用现代通俗的话说，就是当年的计算机，不直接支持 A[3] 这样的数组索引操作，即使这个操作从逻辑上说很简单：把数组 A 的地址加上 3，就得到了 A[3] 的地址，然后去访问这个地址。</p>
<p>这个困难在今天的程序员看来是不可思议的。 为什么这么简单的数组下标寻址机制最一开始的计算机没有支持呢？ 原来，当年的计算机内存很小，只有一千到两千的存储空间，所以，描述地址只需要几位二/十进制数（<a href="http://en.wikipedia.org/wiki/Binary-coded_decimal">BCD</a>)。从而，在每条指令后面直接加一个物理地址是可行且高效的寻址方式。这种寻址方式，叫做直接寻址，当时所有的机器，都只支持直接寻址，因为在机器码中直接指出操作数的准确地址是最简单直接的方法，计算机不需要任何复杂的地址解码电路。但坏处是，这个设计太不灵活了，比如说 A[3] 这个例子，就没法用直接寻址来表示。</p>
<p>一般情况下，如果知道数组A， 对于 A[3] 这样的例子，用直接寻址问题去模拟间接寻址的困难还不是很大，只要程序员事先记住数组 A 的地址然后手工加上 3 就行了 （A也是程序员分配的，因为当时没有操作系统，所以程序员手工管理内存的一切）。可是，也有一些情况这样直接寻址是不行的。比如说，当时计算机已经能支持跳转和判断指令了，也就是说，可以写循环语句了。我们可以很容易看到， 以 i 为循环变量的循环体内，对 A[i] 的访问是不能写成一个静态的直接寻址的，因为 i 一直在变化，所以不可能事先一劳永逸的定好 A[i] 的所在位置，然后静态写在程序中。</p>
<p>这样，即使写一个简单的 10×10 矩阵的乘法，程序员就不得不死写 10的三次方即1000 行地址访问，而没办法用几行循环代替。当时的一些聪明人，也想了一些方法去克服这个问题，比如说，他们先取出 A 的地址，然后做一次加法，把结果，也就是当时 A[i] 的地址，注射到一个读内存的 LOAD 指令后面。然后执行那条 LOAD 指令。比如我要读 A[i]，我先看，A的地址是 600，再看看 i 是3， 就加上 i，变成603，然后，把后面的指令改成 LOAD 603， 这样，就可以读到 A[i]。这个小技巧之所以可行，要感谢冯诺依曼爷爷的体系设计。在冯诺依曼计算机中，数据和程序是混在一起不加区分的，所以程序员可以随时像修改数据一样修改将要运行的下一条程序指令。就这样，靠着这个小技巧, 好歹程序员再也不要用1000行代码表示一个矩阵乘法了。</p>
<p><strong>4. SpeedCoding 的出现</strong></p>
<p>计算机本来就是用来做数学计算的，可是科学计算里面最最基本的两个要素&#8211;浮点计算和数组下标访问，在当时的计算机上都缺少支持。这种需求和实际的巨大落差，必然会召唤出一个中间层来消弭这种落差。 其实计算机科学的一般规律就是这样：当 A 和 C 相差巨大的时候，我们就引入一个中间层 B，用 B 来弥合 A 和 C 之间的不兼容。 当年的这个中间层，就叫做 SpeedCoding，由 IBM 的工程师 John Backus 开发。</p>
<p>SpeedCoding，顾名思义，就是让程序员编程更快。它其实是一个简单，运行在 IBM 701 计算机上的解释器。它允许程序员直接写浮点计算和下标寻址的指令，并且在底层把这些 “伪指令” 翻译成对应的机器码，用软件模拟浮点计算，自动修改地址等等。这样，程序员就可以从没完没了的手工实现浮点运算和下标寻址实现中解放出来，快速的编程。这个 SpeedCoding，这可以算得上是 FORTRAN 的种子了。</p>
<p>虽然这个解释器超级慢，程序员用这个解释器也用得很爽，也不感到它非常慢。 这是因为当年计算机浮点计算都绕不过软件模拟，即使最好的程序员用机器码而不用这个解释器，写出来的程序，也不比这个解释器下运行快多少。另一个更加重要的原因是，这个解释器极大的减少了程序员 debug 和 code 的时间。随着计算机速度的提高，当年一个程序耗费的计算成本和程序员编程耗费的人力成本基本上已经持平了，所以，相比较于写更加底层的机器码，用了 SpeedCoding 的程序员的程序虽然慢点，但人力成本瞬间降成 0，总体下来，用 SpeedCoding 比起不用来，总体成本还要低不少。</p>
<p>好景不长，因为客户一直的要求和电子工业的发展，IBM 在 1954 年，终于发布了划时代的 704 计算机，很多经典的语言和程序，都首次在 704 上完成了。比如之前我们在本系列的D篇中提到的 Steve Russell 的 LISP 解释器，就是在 704 上完成的。 704 计算机一下子支持了浮点计算和间接下标寻址。 这下用 SpeedCoding 的人没优势了，因为机器码支持浮点和下标寻址之后，写机器码比写 SpeedCoding 复杂不了多少，但是速度快了很多倍，因为 SpeedCoding 解释器太慢了，以前因为浮点和解释器一样慢，所以大家不在意它慢，现在浮点和寻址快了，就剩下解释器慢，写机器码的反而占了上风，程序员也就不用 SpeedCoding 了。</p>
<p><strong>5. FORTRAN 创世纪</strong></p>
<p>在 704 出来之前，做 SpeedCoding 的 John Backus 就认识到，要想让大家用他的 SpeedCoding, 或者说，想要从软件工具上入手，减少程序的开发成本，只有两个方法： 1. 程序员可以方便的写数学公式  2. 这个系统最后能够解析/生成足够的快的程序。他认为，只有达到了这两点，程序员才会乐意使用高级的像 SpeedCoding 这样的工具，而不是随着硬件的发展在机器码和 SpeedCoding 这样的工具之间跳来跳去。他本人通过实现 SpeedCoding, 也认识到如果有一个比机器码高级的语言， 生产效率会高很多倍。那么，现在唯一的问题就是实现它，当然，这就不是一个小项目了，就需要 IBM 来支持他的开发了。 所以，在 1953年，他把他的想法写成了一个文档，送给了 IBM 的经理。项目在 1954 年， 704 发布的当年，终于启动。John Backus 领导的设计一个能达到上面两点的编程系统的项目的成果，就是日后的 FORTRAN。</p>
<p>和现在大多数编程语言不一样，FORTRAN 语言的设计的主要问题不是语法和功能，而是编译器怎么写才能高性能。John Backus 日后回忆说，当时谁也没把精力放在语言细节上，语言设计很潦草的就完成了（所以其后正式发布后又经过了N多修订），他们所有的功夫都是花在怎么写一个高性能的编译器上。这个高性能的编译器很难写，到 1957 年才写好，总共花了 IBM 216 个人月。等到 FORTRAN 一推出，不到一年的时间，在 IBM 总共售出的 60 台 704上，就部署了超过一半。现在没啥编程语言能够这么牛的攻城掠地了 ：）</p>
<p><strong>6. 结语</strong></p>
<p>放到历史的上下文中看，FORTRAN 的出现是很自然的。一方面，复杂的数学运算使得一个能够表述数学计算的高级语言成为必须，计算机的发展也为这个需求提供的硬件条件；另一方面，随着计算机的发展，程序员的时间成本一直不变，但是计算的成本一直在降低，用高级语言和用机器码在性能上的些许差异变得可以忽略。这样的历史现实，必然会召唤出以少量的增加计算机工作量为代价，但能大幅度降低程序员时间成本的新的工具和设计。这种新的工具，新的设计，又对程序设计产生革命性的影响。在整个编程发展的历史上，FORTRAN 和其他高级语言的出现可以说是第一波的革命；而后， UNIX和C语言的兴盛，使得系统编程的效率得到革命性提升，可以算是第二波革命；而面向对象方法，使得复杂的 GUI 等系统的编程效率得到提升，应该算得上是第三波革命。到如今，现在各种各样的方法论就更加多了，且看以后回看，哪种方法和工具能够大浪淘沙留下来。</p>
<div id="google_plus_one"><g:plusone></g:plusone></div>]]></content:encoded>
			<wfw:commentRss>http://blog.youxu.info/2009/07/02/fortran/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
	</channel>
</rss>

