Lisp的本质学习思考.docx

上传人:b****7 文档编号:10785674 上传时间:2023-02-22 格式:DOCX 页数:25 大小:40.35KB
下载 相关 举报
Lisp的本质学习思考.docx_第1页
第1页 / 共25页
Lisp的本质学习思考.docx_第2页
第2页 / 共25页
Lisp的本质学习思考.docx_第3页
第3页 / 共25页
Lisp的本质学习思考.docx_第4页
第4页 / 共25页
Lisp的本质学习思考.docx_第5页
第5页 / 共25页
点击查看更多>>
下载资源
资源描述

Lisp的本质学习思考.docx

《Lisp的本质学习思考.docx》由会员分享,可在线阅读,更多相关《Lisp的本质学习思考.docx(25页珍藏版)》请在冰豆网上搜索。

Lisp的本质学习思考.docx

Lisp的本质学习思考

Lisp的本质(TheNatureofLisp)学习思考

2008-01-2017:

37:

39阅读98评论2字号:

大中小

作者SlavaAkhmechet

译者AlecJang

出处:

http:

//www.defmacro.org/ramblings/lisp.html

简介

最初在web的某些角落偶然看到有人赞美Lisp时,我那时已经是一个颇有经验的程序员。

在我的履历上,掌握的语言范围相当广泛,象C++,Java,C#主流语言等等都不在话下,

我觉得我差不多知道所有的有关编程语言的事情。

对待编程语言的问题上,我觉得自己不

太会遇到什么大问题。

其实我大错特错了。

我试着学了一下Lisp,结果马上就撞了墙。

我被那些范例代码吓坏了。

我想很多初次接触

Lisp语言的人,一定也有过类似的感受。

Lisp的语法太次了。

一个语言的发明人,居然不

肯用心弄出一套漂亮的语法,那谁还会愿意学它。

反正,我是确确实实被那些难看的无数

的括号搞蒙了。

回过神来之后,我和Lisp社区的那伙人交谈,诉说我的沮丧心情。

结果,立马就有一大套

理论砸过来,这套理论在Lisp社区处处可见,几成惯例。

比如说:

Lisp的括号只是表面现

象;Lisp的代码和数据的表达方式没有差别,而且比XML语法高明许多,所以有无穷的好

处;Lisp有强大无比的元语言能力,程序员可以写出自我维护的代码;Lisp可以创造出针

对特定应用的语言子集;Lisp的运行时和编译时没有明确的分界;等等,等等,等等。

么长的赞美词虽然看起来相当动人,不过对我毫无意义。

没人能给我演示这些东西是如何

应用的,因为这些东西一般来说只有在大型系统才会用到。

我争辩说,这些东西传统语言

一样办得到。

在和别人争论了数个小时之后,我最终还是放弃了学Lisp的念头。

为什么要

花费几个月的时间学习语法这么难看的语言呢?

这种语言的概念这么晦涩,又没什么好懂

的例子。

也许这语言不是该我这样的人学的。

几个月来,我承受着这些Lisp辩护士对我心灵的重压。

我一度陷入了困惑。

我认识一些绝

顶聪明的人,我对他们相当尊敬,我看到他们对Lisp的赞美达到了宗教般的高度。

这就是

说,Lisp中一定有某种神秘的东西存在,我不能忍受自己对此的无知,好奇心和求知欲最

终不可遏制。

我于是咬紧牙关埋头学习Lisp,经过几个月的时间费劲心力的练习,终于,

我看到了那无穷无尽的泉水的源头。

在经过脱胎换骨的磨练之后,在经过七重地狱的煎熬

之后,终于,我明白了。

顿悟在突然之间来临。

曾经许多次,我听到别人引用雷蒙德(译者注:

论文<<大教堂和市

集>>的作者,著名的黑客社区理论家)的话:

"Lisp语言值得学习。

当你学会Lisp之后,你

会拥有深刻的体验。

就算你平常并不用Lisp编程,它也会使你成为更加优秀的程序员"。

过去,我根本不懂这些话的含义,我也不相信这是真的。

可是现在我懂得了。

这些话蕴含

的真理远远超过我过去的想像。

我内心体会到一种神圣的情感,一瞬间的顿悟,几乎使我

对电脑科学的观念发生了根本的改变。

顿悟的那一刻,我成了Lisp的崇拜者。

我体验到了宗教大师的感受:

一定要把我的知识传

布开来,至少要让10个迷失的灵魂得到拯救。

按照通常的办法,我把这些道理(就是刚开

始别人砸过来的那一套,不过现在我明白了真实的含义)告诉旁人。

结果太令人失望了,

只有少数几个人在我坚持之下,发生了一点兴趣,但是仅仅看了几眼Lisp代码,他们就退

却了。

照这样的办法,也许费数年功夫能造就了几个Lisp迷,但我觉得这样的结果太差强

人意了,我得想一套有更好的办法。

我深入地思考了这个问题。

是不是Lisp有什么很艰深的东西,令得那么多老练的程序员都

不能领会?

不是,没有任何绝对艰深的东西。

因为我能弄懂,我相信其他人也一定能。

么问题出在那里?

后来我终于找到了答案。

我的结论就是,凡是教人学高级概念,一定要

从他已经懂得的东西开始。

如果学习过程很有趣,学习的内容表达得很恰当,新概念就会

变得相当直观。

这就是我的答案。

所谓元编程,所谓数据和代码形式合一,所谓自修改代

码,所谓特定应用的子语言,所有这些概念根本就是同族概念,彼此互为解释,肯定越讲

越不明白。

还是从实际的例子出发最有用。

我把我的想法说给Lisp程序员听,遭到了他们的反对。

"这些东西本身当然不可能用熟悉

的知识来解释,这些概念完全与众不同,你不可能在别人已有的经验里找到类似的东西",

可是我认为这些都是遁词。

他们又反问我,"你自己为啥不试一下?

"好吧,我来试一下。

这篇文章就是我尝试的结果。

我要用熟悉的直观的方法来解释Lisp,我希望有勇气的人读

完它,拿杯饮料,深呼吸一下,准备被搞得晕头转向。

来吧,愿你获得大能。

重新审视XML

千里之行始于足下。

让我们的第一步从XML开始。

可是XML已经说得更多的了,还能有什么

新意思可说呢?

有的。

XML自身虽然谈谈不上有趣,但是XML和Lisp的关系却相当有趣。

XML和Lisp的概念有着惊人的相似之处。

XML是我们通向理解Lisp的桥梁。

好吧,我们且把

XML当作活马医。

让我们拿好手杖,对XML的无人涉及的荒原地带作一番探险。

我们要从一

个全新的视角来考察这个题目。

表面上看,XML是一种标准化语法,它以适合人阅读的格式来表达任意的层次化数据

(hirearchicaldata)。

象任务表(to-dolist),网页,病历,汽车保险单,配置文件等

等,都是XML用武的地方。

比如我们拿任务表做例子:

Cleanthehouse.

Washthedishes.

Buymoresoap.

解析这段数据时会发生什么情况?

解析之后的数据在内存中怎样表示?

显然,用树来表示

这种层次化数据是很恰当的。

说到底,XML这种比较容易阅读的数据格式,就是树型结构

数据经过序列化之后的结果。

任何可以用树来表示的数据,同样可以用XML来表示,反之

亦然。

希望你能懂得这一点,这对下面的内容极其重要。

再进一步。

还有什么类型的数据也常用树来表示?

无疑列表(list)也是一种。

上过编译课

吧?

还模模糊糊记得一点吧?

源代码在解析之后也是用树结构来存放的,任何编译程序都

会把源代码解析成一棵抽象语法树,这样的表示法很恰当,因为源代码就是层次结构的:

函数包含参数和代码块,代码快包含表达式和语句,语句包含变量和运算符等等。

我们已经知道,任何树结构都可以轻而易举的写成XML,而任何代码都会解析成树,因此,

任何代码都可以转换成XML,对不对?

我举个例子,请看下面的函数:

intadd(intarg1,intarg2)

{

returnarg1+arg2;

}

能把这个函数变成对等的XML格式吗?

当然可以。

我们可以用很多种方式做到,下面是其

中的一种,十分简单:

arg1

arg2

这个例子非常简单,用哪种语言来做都不会有太大问题。

我们可以把任何程序码转成XML,

也可以把XML转回到原来的程序码。

我们可以写一个转换器,把Java代码转成XML,另一个

转换器把XML转回到Java。

一样的道理,这种手段也可以用来对付C++(这样做跟发疯差不

多么。

可是的确有人在做,看看GCC-XML(http:

//www.gccxml.org)就知道了)。

进一步说,

凡是有相同语言特性而语法不同的语言,都可以把XML当作中介来互相转换代码。

实际上

几乎所有的主流语言都在一定程度上满足这个条件。

我们可以把XML作为一种中间表示法,

在两种语言之间互相译码。

比方说,我们可以用Java2XML把Java代码转换成XML,然后用

XML2CPP再把XML转换成C++代码,运气好的话,就是说,如果我们小心避免使用那些C++不

具备的Java特性的话,我们可以得到完好的C++程序。

这办法怎么样,漂亮吧?

这一切充分说明,我们可以把XML作为源代码的通用存储方式,其实我们能够产生一整套

使用统一语法的程序语言,也能写出转换器,把已有代码转换成XML格式。

如果真的采纳

这种办法,各种语言的编译器就用不着自己写语法解析了,它们可以直接用XML的语法解

析来直接生成抽象语法树。

说到这里你该问了,我们研究了这半天XML,这和Lisp有什么关系呢?

毕竟XML出来之时,

Lisp早已经问世三十年了。

这里我可以保证,你马上就会明白。

不过在继续解释之前,我

们先做一个小小的思维练习。

看一下上面这个XML版本的add函数例子,你怎样给它分类,

是代码还是数据?

不用太多考虑都能明白,把它分到哪一类都讲得通。

它是XML,它是标

准格式的数据。

我们也知道,它可以通过内存中的树结构来生成(GCC-XML做的就是这个事

情)。

它保存在不可执行的文件中。

我们可以把它解析成树节点,然后做任意的转换。

而易见,它是数据。

不过且慢,虽然它语法有点陌生,可它又确确实实是一个add函数,

对吧?

一旦经过解析,它就可以拿给编译器编译执行。

我们可以轻而易举写出这个XML

代码解释器,并且直接运行它。

或者我们也可以把它译成Java或C++代码,然后再编译运

行。

所以说,它也是代码。

我们说到那里了?

不错,我们已经发现了一个有趣的关键之点。

过去被认为很难解的概念

已经非常直观非常简单的显现出来。

代码也是数据,并且从来都是如此。

这听起来疯疯癫

癫的,实际上却是必然之事。

我许诺过会以一种全新的方式来解释Lisp,我要重申我的许

诺。

但是我们此刻还没有到预定的地方,所以还是先继续上边的讨论。

刚才我说过,我们可以非常简单地实现XML版的add函数解释器,这听起来好像不过是说说

而已。

谁真的会动手做一下呢?

未必有多少人会认真对待这件事。

随便说说,并不打算真

的去做,这样的事情你在生活中恐怕也遇到吧。

你明白我这样说的意思吧,我说的有没有

打动你?

有哇,那好,我们继续。

重新审视Ant

我们现在已经来到了月亮背光的那一面,先别忙着离开。

再探索一下,看看我们还能发现

什么东西。

闭上眼睛,想一想2000年冬天的那个雨夜,一个名叫JamesDuncanDavidson

的杰出的程序员正在研究Tomcat的servlet容器。

那时,他正小心地保存好刚修改过的文

件,然后执行make。

结果冒出了一大堆错误,显然有什么东西搞错了。

经过仔细检查,他

想,难道是因为tab前面加了个空格而导致命令不能执行吗?

确实如此。

老是这样,他真

的受够了。

乌云背后的月亮给了他启示,他创建了一个新的Java项目,然后写了一个简单

但是十分有用的工具,这个工具巧妙地利用了Java属性文件中的信息来构造工程,现在

James可以写makefile的替代品,它能起到相同的作用,而形式更加优美,也不用担心有

makefile那样可恨的空格问题。

这个工具能够自动解释属性文件,然后采取正确的动作来

编译工程。

真是简单而优美。

(作者注:

我不认识James,James也不认识我,这个故事是根据网上关于Ant历史的帖子

虚构的)

使用Ant构造Tomcat之后几个月,他越来越感到Java的属性文件不足以表达复杂的构造指

令。

文件需要检出,拷贝,编译,发到另外一台机器,进行单元测试。

要是出错,就发邮

件给相关人员,要是成功,就继续在尽可能高层的卷(volumn)上执行构造。

追踪到最后,

卷要回复到最初的水平上。

确实,Java的属性文件不够用了,James需要更有弹性的解决

方案。

他不想自己写解析器(因为他更希望有一个具有工业标准的方案)。

XML看起来是个

不错的选择。

他花了几天工夫把Ant移植到XML,于是,一件伟大的工具诞生了。

Ant是怎样工作的?

原理非常简单。

Ant把包含有构造命令的XML文件(算代码还是算数据,

你自己想吧),交给一个Java程序来解析每一个元素,实际情况比我说的还要简单得多。

一个简单的XML指令会导致具有相同名字的Java类装入,并执行其代码。

这段文字的含义是把源目录复制到目标目录,Ant会找到一个"copy"任务(实际上就是一个

Java类),通过调用Java的方法来设置适当参数(todir和fileset),然后执行这个任务。

Ant带有一组核心类,可以由用户任意扩展,只要遵守若干约定就可以。

Ant找到这些类,

每当遇到XML元素有同样的名字,就执行相应的代码。

过程非常简单。

Ant做到了我们前面

所说的东西:

它是一个语言解释器,以XML作为语法,把XML元素转译为适当的Java指令。

我们可以写一个"add"任务,然后,当发现XML中有add描述的时候,就执行这个add任务。

由于Ant是非常流行的项目,前面展示的策略就显得更为明智。

毕竟,这个工具每天差不

多有几千家公司在使用。

到目前为之,我还没有说Ant在解析XML时所遇到困难。

你也不用麻烦去它的网站上去找答

案了,不会找到有价值的东西。

至少对我们这个论题来说是如此。

我们还是继续下一步讨

论吧。

我们答案就在那里。

为什么是XML

有时候正确的决策并非完全出于深思熟虑。

我不知道James选择XML是否出于深思熟虑。

许仅仅是个下意识的决定。

至少从James在Ant网站上发表的文章看起来,他所说的理由完

全是似是而非。

他的主要理由是移植性和扩展性,在Ant案例上,我看不出这两条有什么

帮助。

使用XML而不是Java代码,到底有什么好处?

为什么不写一组Java类,提供api来满

足基本任务(拷贝目录,编译等等),然后在Java里直接调用这些代码?

这样做仍然可以保

证移植性,扩展性也是毫无疑问的。

而且语法也更为熟悉,看着顺眼。

那为什么要用XML

呢?

有什么更好的理由吗?

有的。

虽然我不确定James是否确实意识到了。

在语义的可构造性方面,XML的弹性是Java

望尘莫及的。

我不想用高深莫测的名词来吓唬你,其中的道理相当简单,解释起来并不费

很多功夫。

好,做好预备动作,我们马上就要朝向顿悟的时刻做奋力一跃。

上面的那个copy的例子,用Java代码怎样实现呢?

我们可以这样做:

CopyTaskcopy=newCopyTask();

Filesetfileset=newFileset();

fileset.setDir("src_dir");

copy.setToDir("../new/dir");

copy.setFileset(fileset);

copy.excute();

这个代码看起来和XML的那个很相似,只是稍微长一点。

差别在那里?

差别在于XML构造了

一个特殊的copy动词,如果我们硬要用Java来写的话,应该是这个样子:

copy("../new/dir");

{

fileset("src_dir");

}

看到差别了吗?

以上代码(如果可以在Java中用的化),是一个特殊的copy算符,有点像

for循环或者Java5中的foreach循环。

如果我们有一个转换器,可以把XML转换到Java,大

概就会得到上面这段事实上不可以执行的代码。

因为Java的技术规范是定死的,我们没有

办法在程序里改变它。

我们可以增加包,增加类,增加方法,但是我们没办法增加算符,

而对于XML,我们显然可以任由自己增加这样的东西。

对于XML的语法树来说,只要原意,

我们可以任意增加任何元素,因此等于我们可以任意增加算符。

如果你还不太明白的话,

看下面这个例子,加入我们要给Java引入一个unless算符:

unless(someObject.canFly())

{

someObject.transportByGround():

}

在上面的两个例子中,我们打算给Java语法扩展两个算符,成组拷贝文件算符和条件算符

unless,我们要想做到这一点,就必须修改Java编译器能够接受的抽象语法树,显然我们

无法用Java标准的功能来实现它。

但是在XML中我们可以轻而易举地做到。

我们的解析器

根据XML元素,生成抽象语法树,由此生成算符,所以,我们可以任意引入任何算符。

对于复杂的算符来说,这样做的好处显而易见。

比如,用特定的算符来做检出源码,编译

文件,单元测试,发送邮件等任务,想想看有多么美妙。

对于特定的题目,比如说构造软

件项目,这些算符的使用可以大幅减低少代码的数量。

增加代码的清晰程度和可重用性。

解释性的XML可以很容易的达到这个目标。

XML是存储层次化数据的简单数据文件,而在

Java中,由于层次结构是定死的(你很快就会看到,Lisp的情况与此截然不同),我们就没

法达到上述目标。

也许这正是Ant的成功之处呢。

你可以注意一下最近Java和C#的变化(尤其是C#3.0的技术规范),C#把常用的功能抽象出

来,作为算符增加到C#中。

C#新增加的query算符就是一个例子。

它用的还是传统的作法:

C#的设计者修改抽象语法树,然后增加对应的实现。

如果程序员自己也能修改抽象语法树

该有多好!

那样我们就可以构造用于特定问题的子语言(比如说就像Ant这种用于构造项目

的语言),你能想到别的例子吗?

再思考一下这个概念。

不过也不必思考太甚,我们待会

还会回到这个题目。

那时候就会更加清晰。

离Lisp越来越近

我们先把算符的事情放一放,考虑一下Ant设计局限之外的东西。

我早先说过,Ant可以通

过写Java类来扩展。

Ant解析器会根据名字来匹配XML元素和Java类,一旦找到匹配,就执

行相应任务。

为什么不用Ant自己来扩展Ant呢?

毕竟核心任务要包含很多传统语言的结构

(例如"if"),如果Ant自身就能提供构造任务的能力(而不是依赖java类),我们就可以得

到更高的移植性。

我们将会依赖一组核心任务(如果你原意,也不妨把它称作标准库),而

不用管有没有Java环境了。

这组核心任务可以用任何方式来实现,而其他任务建筑在这

组核心任务之上,那样的话,Ant就会成为通用的,可扩展的,基于XML的编程语言。

考虑

下面这种代码的可能性:

如果XML支持"task"的创建,上面这段代码就会输出"HelloWorld!

".实际上,我们可以

用Java写个"task"任务,然后用Ant-XML来扩展它。

Ant可以在简单原语的基础上写出更复

杂的原语,就像其他编程语言常用的作法一样。

这也就是我们一开始提到的基于XML的编

程语言。

这样做用处不大(你知道为甚么吗?

),但是真的很酷。

再看一回我们刚才说的Task任务。

祝贺你呀,你在看Lisp代码!

!

!

我说什么?

一点都不像

Lisp吗?

没关系,我们再给它收拾一下。

比XML更好

前面一节说过,Ant自我扩展没什么大用,原因在于XML很烦琐。

对于数据来说,这个问题

还不太大,但如果代码很烦琐的话,光是打字上的麻烦就足以抵消它的好处。

你写过Ant

的脚本吗?

我写过,当脚本达到一定复杂度的时候,XML非常让人厌烦。

想想看吧,为了

写结束标签,每个词都得打两遍,不发疯算好的!

为了解决这个问题,我们应当简化写法。

须知,XML仅仅是一种表达层次化数据的方式。

我们并不是一定要使用尖括号才能得到树的序列化结果。

我们完全可以采用其他的格式。

其中的一种(刚好就是Lisp所采用的)格式,叫做s表达式。

s表达式要做的和XML一样,但

它的好处是写法更简单,简单的写法更适合代码输入。

后面我会详细讲s表达式。

这之前

我要清理一下XML的东西。

考虑一下关于拷贝文件的例子:

想想看在内存里面,这段代码的解析树在内存会是什么样子?

会有一个"copy"节点,其下

有一个"fileset"节点,但是属性在哪里呢?

它怎样表达呢?

如果你以前用过XML,并且

弄不清楚该用元素还是该用属性,你不用感到孤单,别人一样糊涂着呢。

没人真的搞得清

楚。

这个选择与其说是基于技术的理由,还不如说是闭着眼瞎摸。

从概念上来讲,属性也

是一种元素,任何属性能做的,元素一样做得到。

XML引入属性的理由,其实就是为了让

XML写法不那么冗长。

比如我们看个例子:

../new/dir

src_dir

两下比较,内容的信息量完全一样,用属性可以减少打字数量。

如果XML没有属性的话,

光是打字就够把人搞疯掉。

说完了属性的问题,我们再来看一看s表达式。

之所以绕这么个弯,是因为s表达式没有属

性的概念。

因为s表达式非常简练,根本没有必要引入属性。

我们在把XML转换成s表达式

的时候,心里应该记住这一点。

看个例子,上面的代码译成s表达式是这样的:

(copy

(todir"../new/dir")

(fileset(dir"src_dir")))

仔细看看这个例子,差别在哪里?

尖括号改成了圆括号,每个元素原来是有一对括号标记

包围的,现在取消了后一个(就是带斜杠的那个)括号标记。

表示元素的结束只需要一个")"

就可以了。

不错,差别就是这些。

这两种表达方式的转换,非常自然,也非常简单。

s表

达式打起字来,也省事得多。

第一次看s表达式(Lisp)时,括号很烦人是吧?

现在我们明

白了背后的道理,一下子就变得容易多了。

至少,比XML要好的多。

用s表达式写代码,不

单是实用,而且也很让人愉快。

s表达式具有XML的一切好处,这些好处是我们刚刚探讨过

的。

现在我们看看更加Lisp风格的task例子:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 文化宗教

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1