与Scheme共舞

 发表在《程序员》2007年7月刊上。不log上写帖子不用考虑版面限制,所以这里的帖子比发表的啰嗦点。赵健平编辑,Jacky,和刘未鹏都给了我很多帮助,在这里一并谢了。免费的Scheme实现很多。我用的是PLT Scheme,可以到这里下载。PLT Scheme的IDE(Dr. Scheme)支持Emacs的键盘绑定,用emacs的老大们应该喜欢。Dr.Scheme内置中文支持:

下面是正文:
不能影响你思考方式的编程语言不值得学习 – Alan Perlis [1]
 
不少朋友问,为什么要学Scheme这样无数括号包裹的语言。答案很简单:帮你理解计算的本质,成为更优秀的程序员。Scheme好比大还丹。没人拿药丸儿当板砖拍人,但服了它却能指望十步杀一人,千里不留行。
 
1975年问世的Scheme是Lisp方言。所以不妨从Lisp谈起。Lisp是一门传奇语言,诞生50年,仍然影响深远。程序员们似乎不断“发现”Lisp里简单却深刻,浅显但强大的特性,并应用到不同地方,取得非凡成就。比如最近热火的Ruby、Python、以及JavaScript中许多为人称道的功能源于Lisp。也许John K. Foderaro的比喻和总结最能说明Lisp的价值:Lisp好比变色龙,高度适应环境的改变,因为 它是一门可以编程的编程语言。我们不仅可以用Lisp编程,还可以对Lisp编程 [2]。Lisp内置的抽象方法让Lisp程序员们身段灵活,长袖善舞。每当新的编程范式出现,Lisp程序员们总能快速实现相关功能,甚至做出进一步改进。比如Smalltalk展示面向对象编程的潜力后,MIT媒体实验室的Cannon Howard便在1982年推出Flavors,一个功能丰富的面向对象扩展。Cannon的扩展不仅实现了 当时流行的面向对象功能,还首创了多继承,多重分派,以及被Ruby程序员狂赞的mixin [3]。尔后在Xerox PARC的 Gregor Kiczales又在集大成的Common Lisp面向对象扩展 — CLOS — 里加入面向方面(AOP)的编程方法 [4]。Gregor也是面向方面编程的发起人和AspectJ的作者。熟悉Java的老大应该对他不陌生。其实CLOS支持的method combination已经支持AOP里的before/after/around处理。AOP和CLOS出于同一人之手,应该不是巧合。顺便说一句,Gregor1993年的名作 The Art of Meta Object Protocol也值得细读。
 
传奇语言自有传奇历史。1958年,John McCarthy从达特茅斯搬到MIT。当时人工智能的另一奠基人Marvin Minsky也在那里。牛人相见,好比利刃相击,火花耀眼。著名的MIT人工智能计划上马 [5]。研究AI的过程中,McCarthy需要一门编程语言描述他的理论模型。当时人见人爱的图灵机只有一套笨拙的语言,不适合干净利落地表达复杂的递归函数,所以McCarthy在丘齐的lambda算子基础上设计了Lisp。早期的Lisp是纯理论工具,用来帮助项目组进行程序的推导和证明。实在需要用机器验证理论了,研究组的老大们就手工把Lisp程序翻译成IBM 740的汇编代码,再上载到IBM 740上运行。人肉编译器们甚至热衷于编译各式Lisp程序,觉得跟解智力题一样好玩儿。他们还证明了可以用Lisp写出一个通用函数eval(), 用来解释执行Lisp的表达式 [6]。但他们光顾赞叹eval()和元图灵机一样彪悍,且比图灵机构造出元图灵机的代码美妙,并没想到eval就是一个通用的Lisp解释器。幸好有天McCarthy的学生S.R. Russell灵机闪现,连夜用IBM704的机器语言实现eval()。于是世界上第一个Lisp解释器横空出世,绿色低功耗无污染的人肉编译才渐渐失传。那时真是计算机科学研究的黄金时代啊,人们可以一夜之间改变世界,比居委会大妈在股市一夜暴富还来得轻快。顺便提一下,我们习以为常的条件判断语句,也是McCarthy在Lisp里发明的。而为了让函数应用没有副作用和实现函数闭包,McCarthy的研究小组又顺便发明了垃圾收集。1975年,同是MIT的Gerald Jay Sussman和Guy Steele为了研究Carl Hewitt的面向对象模型,用Lisp编写了一个玩具语言。这个玩具语言简化了当时流行的Lisp语法,引入了词法定界(又叫静态范围)和Continuation两大革新。Sussman和Steele给这门语言取名Schemer,希望它发展成像AI里著名系统Planner一样的有力工具。可惜当时MIT用的操作系统ITS只允许最长6个字节的文件名。Sussman和Steele不得不把Schemer的最后一个字幕’r’去掉。Scheme问世便显露峥嵘:Sussman和Steele很快发现Scheme的函数和Hewitt模型里的演员(也就是我们现在所谓的对象)没有本质区别,连句法都基本一致 [7]。事实上,Sussman在教材《计算机程序设计与解释》的第二章用短短几十行代码展示了一套面向对象系统。
 
Scheme是极度简化的语言。他的规范文档不过47页 [8]。相比Lisp另一大分支Common Lisp规范的上千页文档或者Java规范的500来页文档,可见Scheme的短小精悍。不过,我们仍然可用Scheme写出优雅犀利的程序。Scheme规范R 5RS开篇道出了Scheme的设计宗旨:设计编程语言时不应堆砌功能,而应去掉让多余功能显得必要的弱点和限制。Smalltalk的发明人Alan Kay在一次访谈录中提到,Lisp是编程语言中的麦克斯韦方程组 [9]。这句评价用到Scheme上更为合适。Scheme甚至让我们写出用其他语言无法轻易写出的程序。Sussman和Steele用Scheme探索不同的编程模型时时,往往一周做出十来种不同的解释器,可以旁证Scheme的简洁和灵活。在解释是什么造就了Scheme的精练与生猛之前,我们先介绍一下Scheme的基本元素:
 
  • Scheme的语法结构 大道至简。Scheme的结构就两种:原子和表达式。原子是诸如数,字符串,布尔值,变量,空表这类简单数据。对非变量的原子求值,得到原子自身。对变量求值,得到变量绑定的值。比如说,对1求值得到1,但如果对变量A求值,而A和字符串”A”绑定,则得到字符串“A”。表达式的形式也只有一种:列表。一对括号包含起来的就是列表。表里的元素用空格分开。列表可以嵌套。这样的表达式在Lisp里叫做S-表达式,意思是符号表达式。下面是一些例子:
    • ( ): 一个空表
    • (1 2 3 4 5):一个包含五个整数的表
    • (1 “a” 1.5 #t #f):一个列表,依次包含整数、字符串、浮点数、为真的布尔值、和为假的布尔值
    • (1 (2 3) ):一个嵌套列表,第二个元素(2 3)也是一个表
    • (+ 2 3):一个表达式,表示把2和3相加。Scheme里所有的操作符都是前缀操作符,即操作符在前,操作数据在后。比如说4 * (2 + 3)在Scheme里表达为(* 4  (+ 2 3))。很多人看不管这种方式。不过仔细思考一下,可以看出前置操作符让任何操作符都是多维的。比如说。如果我们要把1到5的整数相加,用中缀操作符,就得写成 1 + 2 + 3 + 4 + 5。同一个加好重复了4次。而用前缀操作符,只需要写一次:(+ 1 2 3 4 5)。推而广之,如果我们要把一列数加起来,就得用到循环。而在Scheme里则不需要。而且前缀操作符去掉了优先级问题:我们可以通过括号来判断每个表达式的优先级。
    • (lambda (x y) (sqrt (* x y))。这个表达式定义了一个匿名函数,计算并返回参数x和y的几何平均值。当一个表达式以lambda开头的时后,我们就知道要定义一个函数了。
    • (define zero 0):这个表达式把一个变量zero绑定到一个整数0。在Scheme里,所有变量本质上都是指针。指针本身没有类型,他们指向的值才有类型。换句话说,Scheme是动态类型语言。
    • (car  ‘(1 2 3 4)):这个表达式调用函数car。函数car接收一个列表参数,并返回这个参数的第一个值,也就是1。注意例子里的参数(1 2 3 4)前有一单引号。这是因为Scheme总是把一个普通列表当作表达式计算。加上单引号相当于告诉Scheme,不要对(1 2 3 4)估值&#x
  • 11
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值