Dan Friedman 是 IU 的教授,程序语言界的元老之一,Lisp (Scheme) 语言的主要研究者之一,《The Little Schemer》(前身叫《The Little Lisper》)的作者。他对程序语言有非常深刻的理解。Haskell 所用的 lazy evaluation 模型,最早就是他在 1976 年在与 David Wise 合写的论文“CONS should not Evaluate its Arguments”中提出来的。他并不是我正式的导师,但他是这一生中教会我最多东西的人,所以我想写一些关于他的小故事。也许你能从中看出,一个真正的教育者是什么样子的。我来 IU 之前,一位师兄告诉我,Dan Friedman 就像指环王里的甘道夫 (Gandalf),来了之后发现确实很像。
第一次在办公室见到 Friedman 的时候,他对我说:“来,给我讲讲你知道些什么?”我自豪地说:“我在 Cornell 上过研究生的程序语言课,会用 ML 和 Haskell,看过 Paul Graham 的 On Lisp,Peter Norvig 的 Paradigms of Artificial Intelligence Programming, Richard Gabriel 的一些文章...”他看着我微笑:“不错,你已经有一定基础……”
这么几年以后,我才发现他善良的微笑里面,隐藏着难以启齿的秘密:当时的我是多么的幼稚!在他的这种循循善诱之下,我才逐渐的明白了,知识的深度是无止境的。
Dan Friedman 已经远远超过了退休年龄,仍然坚持教学。他的本科生程序语言课程 C311 是 IU 的“星级课程”。我最敬佩的,是他那孩子般的好奇心和探索精神。几乎每一年的 C311,他都会发明不同的东西来充实课程内容。有时候是一种新的逻辑编程语言 (类似 Prolog),有时候是些小技巧 (比如把 Scheme 编译成 C 却不会堆栈溢出),等等……
Friedman研究一个东西的时候总是全身心的投入,执着的热爱。自从开始搞这个叫 miniKanren 的逻辑编程语言,Friedman 多了一句口头禅:“Does it run backwards?”(能反向运行吗?)因为逻辑式的语言(像Prolog)的程序,都是能“反向运行”的。普通程序语言写的程序,如果你给它一个输入,它会给你一个输出。但是逻辑式语言很特别,如果你给它一个输出,它可以反过来运行,给你所有可能的输入。但是
Friedman 有一个本领域的人都知道的“弱点”——他不喜欢静态类型系统 (static type system)。其实 Scheme 专家们大部分都不喜欢静态类型系统。为此,他深受“类型专家”们的误解甚至鄙视,可是他都从容对待之。
有一次在他的进阶课程 B621 上,他给我们出了一道题:用 Scheme 实现 ML 和 Haskell 所用的 Hindley-Milner 类型系统。这种类型系统的工作原理一般是,输入一个程序,它经过对程序进行类型推导(type inference),输出一个类型。如果程序里有类型错误,它就会报错。由于之前在 Cornell 用 ML 实现过这东西,再加上来到 IU 之后对抽象解释 (abstract interpretation) 的进一步理解,我很快做出了这个东西,而且比在 Cornell 的时候做的还要优雅。
他知道我做出来了,很高兴的样子,让我给全班同学(也就8,9个人)讲我的做法。当我自豪的讲完,他问:“Does it run backwards? 如果我给它一个类型,它能自动生成出符合这个类型的程序来吗?”我愣了,欲哭无泪啊,“不能……”他往沙发靠背上一躺,得意的笑了:“我的系统可以!哈哈!我当年写的那个类型系统比你这个还要短呢。我早就知道这些类型系统怎么做,可我就是不喜欢。哈哈哈哈……”
前几天看了 Stephen Kleene 的一篇论文,才发现原来他说的这种由类型反向算出程序的做法叫做 “realizability”,是一个很深刻的理论,可以用来帮助自动证明数学定理。而我后来对类型系统的进一步研究显示,Hindley-Milner 类型系统确实有很多不必要的问题,才导致了他不喜欢。
他就是这样一个老顽童。他喜欢先把你捧上天,再把你打下来,让你知道天外有天 :-)
你永远想象不到 Dan Friedman 的思想的极限在哪里。当你认为他是一个函数式语言专家的时候,他设计了 miniKanren,一种逻辑式编程语言 (logic programming language),并且写出 《The Reasoned Schemer》,用于教授逻辑编程。当你认为他不懂类型系统的时候,他开始捣鼓最尖端的 Martin-Löf类型理论,并且开始设计机器证明系统。而他做这些,完全是出于自己的兴趣。他从来不在乎别人在这个方向已经做到了什么程度,却经常能出乎意料的简化别人的设计。
有一次系里举办教授们的“闪电式演讲”(lightening talk),每位教授只有5分钟时间上去介绍自己的研究。轮到 Friedman 的时候,他慢条斯理的走上去,说:“我不着急。我只有几句话要说。我不知道我能不能拖够5分钟……”大家都笑了。他接着说:“我现在最喜欢的东西是 Curry-Howard correspondence和定理证明。我觉得现在的机器证明系统太复杂了,比如 Coq 有 nnnnn 行代码。我想在 x 年之内,简化 Coq,得到一个 miniCoq……”
miniCoq... 听到这个词全场都笑翻了。为什么呢?自己去联想机器并不是计算的本质。机器可以用任何可行的技术实现,比如集成电路,激光,分子,DNA…… 但是无论用什么作为机器的材料,我们所要表达的语义,也就是计算的本质,却是不变的。
而这些还不是我那届 C311 全部的内容。后半学期,我们开始学习 miniKanren,一种他自己设计的用于教学的逻辑式语言 (logic programming language)。这个语言类似 Prolog,但是它把 Prolog 的很多缺点给去掉了,而且变得更加容易理解。教材是免费送给我们的《The Reasoned Schemer》。在书的最后,两页纸的篇幅,就是整个 miniKanren 语言的实现!我学得比较快,后来就开始捣鼓这个实现,把有些部分重新设计了一下,然后加入了一些我想要的功能。这样的教学,给了我设计逻辑式语言的能力,而不只是停留于一个使用者。这是学习Prolog 不可能做到的事情,因为 Prolog实现的复杂性,会让初学者无从下手,只能停留在使用者的阶段。
我很幸运当初听了他的话,去上了这门课,否则我就不会是今天的我。
Dan Friedman 是一个不随波逐流,有独立思想的人。他的眼里容不下过于复杂的东西,他喜欢把一个东西简化到容得进几行程序,把相关的问 题理解得非常清楚。他的书是一种独特的“问答式”的结构,很像孔夫子或者苏格拉底的讲学方式。他的教学方式也非常独特。这在本科生课程 C311 里已经有一些表现,但是在研究生的课程 B621 里,才全部的显示出来。
我写过的最满意的一个程序,自动CPS 变换,就是在 C311 产生的。在 C311 的作业里,Friedman 经常加入一些“智力题”(brain teaser),做出来了可以加分。因为我已经有一定基础,所以我有精力来做那些智力题。开头那些题还不是很难,直到开始学 CPS 的时候,出现了这么一道:“请写出一个叫 CPSer 的程序,它的作用是自动的把 Scheme 程序转换成 CPS 形式。”那次作业的其它题目都是要求手动把程序变成 CPS 形式,这道智力题却要求一个自动的——用一个程序来转换另一个程序。
我觉得很有意思。如果能写出一个自动的 CPS 转换程序,那我岂不是可以用它完成所有其它的题目了!所以我就开始捣鼓这个东西,最初的想法其实就是“模拟”一个手动转换的过程。然后我发现这真是个怪物,就那么几十行程序,不是这里不对劲,就是那里不对劲。这里按下去一个 bug,那里又冒出来一个,从来没见过这么麻烦的东西。我就跟它死磕了,废寝忘食几乎一星期。经常走进死胡同,就只有重新开始,不知道推翻重来了多少次。到快要交作业的时候,我把它给弄出来了。最后我用它生成了所有其它的答案,产生的 CPS 代码跟手工转换出来的看不出任何区别。当然我这次我又得了满分(因为每次都做智力题,我的分数总是在100以上)。
作业发下来那天下课后,我跟 Friedman 一起走回 Lindley Hall(IU 计算机系的楼)。半路上他问我:“这次的 brain teaser 做了没有。”我说:“做了。这是个好东西啊,帮我把其它作业都做出来了。”他有点吃惊,又有点将信将疑的样子:“你确信你做对了?”我说:“不确信它是完全正确,但是转换后的作业程序全都跟手工做的一样。”走回办公室之后,他给了我一篇30多页的论文 “Representing control: a study of the CPS transformation”,作者是 Olivier Danvy 和 Andrzej Filinski。然后我才了解到,这是这个方向最厉害的两个人。正是这篇论文,解决了这个悬而不决十多年的难题。而自动的 CPS 转换,可以被用于实现高效的函数式语言编译器。Princeton 的 Andrew Appel 教授写了一本书叫《Compiling with Continuations》,就是专门讲这个问题的。而 Amr Sabry(我现在的导师)当年的博士论文就是一个比 CPS 还要简单的变换(叫做
第二个学期,当我去上 Friedman 的进阶课程 B621 的时候,他给我们出了同样的题目。两个星期下来,没有其它人真正的做对了。最后他对全班同学说:“现在请王垠给大家讲一下他的做法。你们要听仔细了哦。这个程序价值100美元!”
而这还不是 B621 的全部,每一个星期,Friedman 会在黑板上写下一道很难的题目。他不让你看书或者看论文。他有时甚至不告诉你题目里相关概念的名字,或者给它们起个新名字,让你想查都查不到。他要求你完 全靠自己把这些难题解出来,下一个星期的时候在黑板上给其它同学讲解。他没有明确的评分标准,让你感觉完全没有成绩的压力。这些题目包括很难的一些问题, 比如
当然,重新发明东西并不会给我带来论文发表,但是它却给我带来了更重要的东西,这就是独立的思考能力。一旦一个东西被你“想”出来,而不是从别人那里 “学”过来,那么你就知道这个想法是如何产生的。这比起直接学会这个想法要有用很多,因为你知道这里面所有的细节和犯过的错误。而最重要的,其实是由此得 到的直觉。如果直接去看别人的书或者论文,你就很难得到这种直觉,因为一般人写论文都会把直觉埋藏在一堆符号公式之下,让你看不到背后的真实想法。如果得到了直觉,下一次遇到类似的问题,你就有可能很快的利用已有的直觉来解决新的问题。
而这一切都已经发生在我身上。比如,在听说 ANF 之后,我没有看 Amr Sabry 的论文,只把原来的 CPSer 程序改了一点点,就得到了 ANF 变换,整个过程只花了十几分钟。而在 R. Kent Dybvig 的编译器课程上,我利用 CPS 变换里面的直觉,改造和合并了 Dybvig 提供的编译器框架的好几个 pass,使得它们变得比原来短小好几倍,而且生成很不错的代码。
现在我仍然是这样,喜欢故意重新发明一些东西,探索不止一个领域。这让我获得了直觉,不再受别人思想的限制,节省了看论文的时间,而且多了一些乐趣。一个问题,当我相信自己能想得出来,一般都能解决。虽然我经常不把我埋头做出来的东西放在心上,把它们叫做“重新发明”(reinvention),但是出乎意料的是,最近我发现这里面其实很是隐藏了一些真正的发明。我准备慢慢把其中一些想法发掘整理出来,发表成论文,或者做成产品。
俗话说,“授人以鱼,不如授人以渔。”就是这个道理吧。Dan Friedman,谢谢你教会我钓鱼。