??xml version="1.0" encoding="utf-8" standalone="yes"?> 下面Q我惛_Z些我心中所想与大家分nQ也希望得到大家的分享?/p>
W一个问题是关于忠诚 当我没了保底,才突然发现这个世界上没有什么事情是可以保证?倘若有的话,那就只有你信c我吸取这ơ的l验和教训,t踏实实的l去L我的梦?/p>
上文已经说了Q我是一个一q离?ơ男。对于公司,我十分抱歉。但凭良心来_对于每一ơ离职我都把自己所有的工作妥善q且负责ȝ做了交接。我也不惛_自己所做的一些事情做什么解释。我也深知一q离?ơ这U工作状态ƈ不是一个工作者应该有的。所q的是我本来p划在毕业后结束这U不正常的状态,因此接下来要扄工作我将会长期的做下午。不知道有没有朋友曾l有q跟我一L状态或l历Q可否说说你是如何度q那D|期的呢?
然后引ZW二个问题,试用?br> 一直以来,在我的理解中Q试用期应该是员工与公司双方的一个试用。因为在q入一家公怹前,你可以通过外界的消息以及公司内部员工的消息来了解公司,但我觉得真正能够影响C的还是你的亲w经历。所以在我看来试用期是提供l双方这么一个机会。面试的时候,hr也问了一个这个相关的问题Q她问我是否能够认q家公司是我想要的那种环境。我的回{是不能。朋友们Q你们又会做出如何的回答Q?br> 再接下来是环境的问题了Q其实这可能也是我被拒的最主要原因
hr?你的q几ơ入职,发现不合适,然后职之后Q你有没有想q自w的原因Q你最l所惌的环境究竟是什么?其实q个问题我真惌Q但是遗憄是我q没有一个清晰的概括Q我一直强调的一个词是宽松,q个词太宽泛了,而且对于不同的公怸同的人的理解也是不同。我前几天曾l恶作剧般的把自q历名U改ZQ我是一个梦?0点上班男Q我是一个梦惛_以随时打游戏P如果你能够理解,随时联系我。承认,我也知道我的q种梦想q稚的可W,我也从未惌哪家公司能够真的L样宽杄条g。但q依然是我的梦想Q我仍然渴望能够有一家公司去努力的提供这U宽松,让我从心底感受到自由。那么,朋友Q你又如何看待一个Programer所需要的环境呢?
作?Slava Akhmechet
译?Alec Jang
出处: http://www.defmacro.org/ramblings/lisp.html
?br>
最初在web的某些角落偶然看到有Lisp? 我那时已l是一个颇有经验的E序员?br>在我的历上, 掌握的语a范围相当q泛, 象C++, Java, C#L语言{等都不在话?
我觉得我差不多知道所有的有关~程语言的事情。对待编E语a的问题上, 我觉得自׃
太会遇到什么大问题。其实我大错牚w了?br>
我试着学了一下Lisp, l果马上撞了墙。我被那些范例代码吓坏了。我惛_多初ơ接?br>Lisp语言的h, 一定也有过cM的感受。Lisp的语法太ơ了。一个语a的发明h, 居然?br>肯用心弄Z套漂亮的语法, 那谁q会愿意学它。反? 我是确实实被那些难看的无数
的括h蒙了?br>
回过来之后, 我和LispC的那伙h交谈, 诉说我的沮心情。结? 立马有一大套
理论砸过? q套理论在LispC处处可见, 几成惯例。比如说: Lisp的括号只是表面现
? Lisp的代码和数据的表达方式没有差? 而且比XML语法高明许多, 所以有无穷的好
? Lisp有强大无比的元语a能力, E序员可以写我维护的代码; Lisp可以创造出?br>对特定应用的语言子集; Lisp的运行时和编译时没有明确的分? {等, {等, {等。这
么长的赞词虽然看v来相当动? 不过Ҏ毫无意义。没l我演示q些东西是如?br>应用? 因ؓq些东西一般来说只有在大型pȝ才会用到。我争辩? q些东西传统语言
一样办得到。在和别ZZC时之后, 我最l还是放弃了学Lisp的念头。ؓ什么要
p几个月的旉学习语法q么隄的语a? q种语言的概念这么晦? 又没什么好?br>的例子。也许这语言不是该我q样的h学的?br>
几个月来, 我承受着q些Lisp辩护士对我心늚重压。我一度陷入了困惑。我认识一些绝
聪明的? 我对他们相当敬, 我看C们对Lisp的赞达C宗教般的高度。这是
? Lisp中一定有某种秘的东西存? 我不能忍受自己对此的无知, 好奇心和求知Ʋ最
l不可遏制。我于是咬紧牙关埋头学习Lisp, l过几个月的旉费劲心力的练? l于,
我看C那无Ih的泉水的源头。在l过p换骨的磨l之? 在经q七重地q煎熬
之后, l于, 我明白了?br>
悟在突然之间来临。曾l许多次, 我听到别人引用雷蒙d(译者注: 论文<<大教堂和?br>?gt;>的作? 著名的黑客社区理论家)的话: "Lisp语言值得学习。当你学会Lisp之后, ?br>会拥有深ȝ体验。就你q_q不用Lisp~程, 它也会你成为更加优U的程序员"?br>q去, 我根本不懂这些话的含? 我也不相信这是真的。可是现在我懂得了。这些话蕴含
的真理远q超q我q去的想像。我内心体会CU神圣的情感, 一瞬间的顿? 几乎使我
对电脑科学的观念发生了根本的改变?br>
悟的那一? 我成了Lisp的崇拜者。我体验C宗教大师的感? 一定要把我的知识传
布开? 臛_要让10个迷q灵魂得到拯救。按照通常的办? 我把q些道理(是刚开
始别人砸q来的那一? 不过现在我明白了真实的含?告诉旁h。结果太令h失望?
只有数几个人在我坚持之? 发生了一点兴? 但是仅仅看了几眼Lisp代码, 他们退
却了。照q样的办? 也许Ҏq功夫能造就了几个Lispq? 但我觉得q样的结果太差强
人意? 我得想一套有更好的办法?br>
我深入地思考了q个问题。是不是Lisp有什么很艰深的东? 令得那么多老练的程序员?br>不能领会? 不是, 没有Ml对艰深的东ѝ因为我能弄? 我相信其他h也一定能。那
么问题出在那? 后来我终于找C{案。我的结论就? 凡是教h学高U概? 一定要
从他已经懂得的东西开始。如果学习过E很有趣, 学习的内容表辑־很恰? 新概念就?br>变得相当直观。这是我的{案。所谓元~程, 所谓数据和代码形式合一, 所谓自修改?br>? 所谓特定应用的子语a, 所有这些概忉|本就是同族概? 彼此互ؓ解释, 肯定讲
不明白。还是从实际的例子出发最有用?br>
我把我的x说给LispE序员听, 遭到了他们的反对?q些东西本n当然不可能用熟悉
的知识来解释, q些概念完全与众不同, 你不可能在别人已有的l验里找到类似的东西",
可是我认些都是遁词。他们又反问? "你自׃ؓ啥不试一?" 好吧, 我来试一下?br>q篇文章是我尝试的l果。我要用熟悉的直观的Ҏ来解释Lisp, 我希望有勇气的h?br>完它, 拿杯饮料, 深呼怸? 准备被搞得晕头{向。来? 愿你获得大能?br>
重新审视XML
千里之行始于下。让我们的第一步从XML开始。可是XML已经说得更多的了, q能有什?br>新意思可说呢? 有的。XML自n虽然谈谈不上有趣, 但是XML和Lisp的关pd相当有趣?br>XML和Lisp的概忉|着惊h的相g处。XML是我们通向理解Lisp的桥梁。好? 我们且把
XML当作z马包R让我们拿好手杖, 对XML的无人涉及的荒原地带作一番探险。我们要从一
个全新的视角来考察q个题目?br>
表面上看, XML是一U标准化语法, 它以适合人阅ȝ格式来表达Q意的层次化数?br>(hirearchical data)。象d?to-do list), |页, 病历, 汽R保险? 配置文g{?br>{? 都是XML用武的地斏V比如我们拿d表做例子:
<todo name="housework">
<item priority="high">Clean the house.</item>
<item priority="medium">Wash the dishes.</item>
<item priority="medium">Buy more soap.</item>
</todo>
解析q段数据时会发生什么情? 解析之后的数据在内存中怎样表示? 昄, 用树来表C?br>q种层次化数据是很恰当的。说到底, XMLq种比较Ҏ阅读的数据格? 是树型l构
数据l过序列化之后的l果。Q何可以用树来表示的数? 同样可以用XML来表C? 反之
亦然。希望你能懂得这一? q对下面的内Ҏ光要?br>
再进一步。还有什么类型的数据也常用树来表C? 无疑列表(list)也是一U。上q编译课
? q模模糊p记得一点吧? 源代码在解析之后也是用树l构来存攄, M~译E序?br>会把源代码解析成一|象语法树, q样的表C法很恰? 因ؓ源代码就是层ơ结构的:
函数包含参数和代码块, 代码快包含表辑ּ和语? 语句包含变量和运符{等?br>
我们已经知道, M树结构都可以轻而易丄写成XML, 而Q何代码都会解析成? 因此,
M代码都可以{换成XML, 对不? 我D个例? L下面的函?
int add(int arg1, int arg2)
{
return arg1+arg2;
}
能把q个函数变成对等的XML格式? 当然可以。我们可以用很多U方式做? 下面是其
中的一U? 十分?
<define-function return-type="int" name="add">
<arguments>
<argument type="int">arg1</argument>
<argument type="int">arg2</argument>
</arguments>
<body>
<return>
<add value1="arg1" value2="arg2" />
</return>
</body>
</define>
q个例子非常? 用哪U语a来做都不会有太大问题。我们可以把ME序码{成XML,
也可以把XML转回到原来的E序码。我们可以写一个{换器, 把Java代码转成XML, 另一?br>转换器把XML转回到Java。一L道理, q种手段也可以用来对付C++(q样做跟发疯差不
多么。可是的有人在? 看看GCC-XML(http://www.gccxml.org)q道了)。进一步说,
凡是有相同语aҎ而语法不同的语言, 都可以把XML当作中介来互相{换代码。实际上
几乎所有的L语言都在一定程度上满q个条g。我们可以把XML作ؓ一U中间表C法,
在两U语a之间互相译码。比方说, 我们可以用Java2XML把Java代码转换成XML, 然后?br>XML2CPP再把XML转换成C++代码, q气好的? 是? 如果我们心避免使用那些C++?br>具备的JavaҎ的? 我们可以得到完好的C++E序。这办法怎么? 漂亮?
q一切充分说? 我们可以把XML作ؓ源代码的通用存储方式, 其实我们能够产生一整套
使用l一语法的程序语a, 也能写出转换? 把已有代码{换成XML格式。如果真的采U?br>q种办法, 各种语言的编译器q不着自己写语法解析了, 它们可以直接用XML的语法解
析来直接生成抽象语法树?br>
说到q里你该问了, 我们研究了这半天XML, q和Lisp有什么关pd? 毕竟XML出来之时,
Lisp早已l问世三十年了。这里我可以保证, 你马上就会明白。不q在l箋解释之前, ?br>们先做一个小的思维l习。看一下上面这个XML版本的add函数例子, 你怎样l它分类,
是代码还是数? 不用太多考虑都能明白, 把它分到哪一c都讲得通。它是XML, 它是?br>准格式的数据。我们也知道, 它可以通过内存中的树结构来生成(GCC-XML做的是q个?br>?。它保存在不可执行的文g中。我们可以把它解析成树节? 然后做Q意的转换。显
而易? 它是数据。不q且? 虽然它语法有炚w? 可它又确实实是一个add函数,
对吧? 一旦经q解? 它就可以拿给~译器编译执行。我们可以轻而易丑ֆ个XML
代码解释? q且直接q行它。或者我们也可以把它译成Java或C++代码, 然后再编译运
行。所以说, 它也是代码?br>
我们说到那里? 不错, 我们已经发现了一个有的关键之点。过去被认ؓ很难解的概念
已经非常直观非常单的昄出来。代码也是数? q且从来都是如此。这听v来疯疯癫
癫的, 实际上却是必然之事。我许诺q会以一U全新的方式来解释Lisp, 我要重申我的?br>诺。但是我们此刻还没有到预定的地方, 所以还是先l箋上边的讨论?br>
刚才我说q? 我们可以非常单地实现XML版的add函数解释? q听h好像不过是说?br>而已。谁真的会动手做一下呢? 未必有多h会认真对待这件事。随便说? q不打算?br>的去? q样的事情你在生zM恐怕也遇到吧。你明白我这栯的意思吧, 我说的有没有
打动? 有哇, 那好, 我们l箋?br>
重新审视Ant
我们现在已经来到了月亮背光的那一? 先别忙着d。再探烦一? 看看我们q能发现
什么东ѝ闭上眼? 想一?000q冬天的那个雨夜, 一个名叫James Duncan Davidson
的杰出的E序员正在研ITomcat的servlet容器。那? 他正心C存好刚修改过的文
? 然后执行make。结果冒Z一大堆错误, 昄有什么东西搞错了。经q仔l检? ?br>? N是因为tab前面加了个空D导致命令不能执行吗? 实如此。老是q样, 他真
的受够了。乌云背后的月亮l了他启C? 他创Z一个新的Java目, 然后写了一个简?br>但是十分有用的工? q个工具巧妙地利用了Java属性文件中的信息来构造工E? 现在
James可以写makefile的替代品, 它能起到相同的作? 而Ş式更加优? 也不用担心有
makefile那样可恨的空格问题。这个工兯够自动解释属性文? 然后采取正确的动作来
~译工程。真是简单而优?br>
(作者注: 我不认识James, James也不认识? q个故事是根据网上关于Ant历史的帖?br>虚构?
使用Ant构造Tomcat之后几个? 他越来越感到Java的属性文件不以表达复杂的构造指
令。文仉要检? 拯, ~译, 发到另外一台机? q行单元试。要是出? 发?br>件给相关人员, 要是成功, ql在可能高层的?volumn)上执行构造。追t到最?
卯回复到最初的水^上。确? Java的属性文件不够用? James需要更有弹性的解决
Ҏ。他不想自己写解析器(因ؓ他更希望有一个具有工业标准的Ҏ)。XML看v来是?br>不错的选择。他׃几天工夫把AntUL到XMLQ于是,一件伟大的工具诞生了?br>
Ant是怎样工作的?原理非常单。Ant把包含有构造命令的XML文g(代码还是算数据,
你自己想?Q交l一个JavaE序来解析每一个元素,实际情况比我说的q要单得多?br>一个简单的XML指o会导致具有相同名字的Javac装入,q执行其代码?br>
<copy todir="../new/dir">
<fileset dir="src_dir" />
</copy>
q段文字的含义是把源目录复制到目标目录,Ant会找C?copy"d(实际上就是一?br>Javac?, 通过调用Java的方法来讄适当参数(todir和fileset)Q然后执行这个Q务?br>Ant带有一l核心类, 可以q户Q意扩? 只要遵守若干U定可以。Ant扑ֈq些c?
每当遇到XML元素有同L名字, 执行相应的代码。过E非常简单。Ant做到了我们前?br>所说的东西: 它是一个语a解释? 以XML作ؓ语法, 把XML元素转译为适当的Java指o?br>我们可以写一?add"d, 然后, 当发现XML中有add描述的时? 执行这个addd?br>׃Ant是非常流行的目, 前面展示的策略就昑־更ؓ明智。毕? q个工具每天差不
多有几千家公司在使用?br>
到目前ؓ? 我还没有说Ant在解析XML时所遇到困难。你也不用麻烦去它的|站上去扄
案了, 不会扑ֈ有h值的东西。至对我们q个论题来说是如此。我们还是l下一步讨
论吧。我们答案就在那里?br>
Z么是XML
有时候正的决策q完全Z深思熟虑。我不知道James选择XML是否Z深思熟虑。也
总仅是个下意识的决定。至从James在Ant|站上发表的文章看v? 他所说的理由?br>全是似是而非。他的主要理由是UL性和扩展? 在Ant案例? 我看不出q两条有什?br>帮助。用XML而不是Java代码, 到底有什么好? Z么不写一lJavac? 提供api来满
_本Q?拯目录, ~译{等), 然后在Java里直接调用这些代? q样做仍然可以保
证移植? 扩展性也是毫无疑问的。而且语法也更为熟? 看着眼。那Z么要?XML
? 有什么更好的理由?
有的。虽然我不确定James是否实意识C。在语义的可构造性方? XML的弹性是Java
望尘莫及的。我不想用高p的名词来吓唬你, 其中的道理相当简? 解释hq不?br>很多功夫。好, 做好预备动作, 我们马上p朝向悟的时d奋力一跃?br>
上面的那个copy的例? 用Java代码怎样实现? 我们可以q样?
CopyTask copy = new CopyTask();
Fileset fileset = new Fileset();
fileset.setDir("src_dir");
copy.setToDir("../new/dir");
copy.setFileset(fileset);
copy.excute();
q个代码看v来和XML的那个很怼, 只是E微长一炏V差别在那里? 差别在于XML构造了
一个特D的copy动词, 如果我们要用Java来写的话, 应该是这个样?
copy("../new/dir");
{
fileset("src_dir");
}
看到差别了吗? 以上代码(如果可以在Java中用的化), 是一个特D的copy符, 有点?br>for循环或者Java5中的foreach循环。如果我们有一个{换器, 可以把XML转换到Java, ?br>概就会得C面这D事实上不可以执行的代码。因为Java的技术规范是定死? 我们没有
办法在程序里改变它。我们可以增加包, 增加c? 增加Ҏ, 但是我们没办法增加算W?
而对于XML, 我们昄可以ȝ自己增加q样的东ѝ对于XML的语法树来说, 只要原意,
我们可以L增加M元素, 因此{于我们可以L增加符。如果你q不太明白的?
看下面这个例? 加入我们要给Java引入一个unless符:
unless(someObject.canFly())
{
someObject.transportByGround():
}
在上面的两个例子? 我们打算lJava语法扩展两个符, 成组拯文g符和条件算W?br>unless, 我们要想做到q一? 必M改Java~译器能够接受的抽象语法? 昄我们
无法用Java标准的功能来实现它。但是在XML中我们可以轻而易丑֜做到。我们的解析?br>Ҏ XML元素, 生成抽象语法? 由此生成符, 所? 我们可以L引入M符?br>
对于复杂的算W来? q样做的好处显而易见。比? 用特定的符来做出源? ~译
文g, 单元试, 发送邮件等d, x看有多么妙。对于特定的题目, 比如说构造Y
仉? q些符的用可以大q减低少代码的数量。增加代码的清晰E度和可重用性?br>解释性的XML可以很容易的辑ֈq个目标。XML是存储层ơ化数据的简单数据文? 而在
Java? ׃层次l构是定ȝ(你很快就会看? Lisp的情况与此截然不?, 我们没
法达Cq目标。也许这正是Ant的成功之处呢?br>
你可以注意一下最qJava和C#的变?其是C#3.0的技术规?, C#把常用的功能抽象?br>? 作ؓ符增加到C#中。C#新增加的query符是一个例子。它用的q是传统的作?
C#的设计者修Ҏ象语法树, 然后增加对应的实现。如果程序员自己也能修改抽象语法?br>该有多好! 那样我们可以构造用于特定问题的子语a(比如说就像Antq种用于构造项?br>的语a), 你能惛_别的例子? 再思考一下这个概c不q也不必思考太? 我们待会
q会回到q个题目。那时候就会更加清晰?br>
Lisp来近
我们先把符的事情放一? 考虑一下Ant设计局限之外的东西。我早先说过, Ant可以?br>q写JavacL扩展。Ant解析器会Ҏ名字来匹配XML元素和Javac? 一旦找到匹? 执
行相应Q务。ؓ什么不用Ant自己来扩展Ant? 毕竟核心d要包含很多传l语a的结?br>(例如"if"), 如果Ant自np提供构造Q务的能力(而不是依赖javac?, 我们可以得
到更高的UL性。我们将会依赖一l核心Q?如果你原? 也不妨把它称作标准库), ?br>不用有没有Java 环境了。这l核心Q务可以用M方式来实? 而其他Q务徏{在q?br>l核心Q务之? 那样的话, Ant׃成ؓ通用? 可扩展的, ZXML的编E语a。考虑
下面q种代码的可能?
<task name="Test">
<echo message="Hello World" />
</task>
<Test />
如果XML支持"task"的创? 上面q段代码׃输出"Hello World!". 实际? 我们可以
用Java写个"task"d, 然后用Ant-XML来扩展它。Ant可以在简单原语的基础上写出更?br>杂的原语, 像其他~程语言常用的作法一栗这也就是我们一开始提到的ZXML的编
E语a。这样做用处不大(你知道ؓ甚么?), 但是真的很酷?br>
再看一回我们刚才说的Taskd。祝Z呀, 你在看Lisp代码!!! 我说什? 一炚w不像
Lisp? 没关p? 我们再给它收拾一下?br>
比XML更好
前面一节说q? Ant自我扩展没什么大? 原因在于XML很烦琐。对于数据来? q个问题
q不太大, 但如果代码很烦琐的话, 光是打字上的ȝp以抵消它的好处。你写过Ant
的脚本吗? 我写q? 当脚本达C定复杂度的时? XML非常让h厌烦。想想看? Z
写结束标{? 每个词都得打两遍, 不发疯算好的!
Z解决q个问题, 我们应当化写法。须? XML仅仅是一U表辑ֱơ化数据的方式?br>我们q不是一定要使用括h能得到树的序列化l果。我们完全可以采用其他的格式?br>其中的一U?刚好是Lisp所采用?格式, 叫做s表达式。s表达式要做的和XML一? ?br>它的好处是写法更? 单的写法更适合代码输入。后面我会详l讲s表达式。这之前
我要清理一下XML的东ѝ考虑一下关于拷贝文件的例子:
<copy toDir="../new/dir">
<fileset dir="src_dir">
</copy>
x看在内存里面, q段代码的解析树在内存会是什么样? 会有一?copy"节点, 其下
有一?"fileset"节点, 但是属性在哪里? 它怎样表达? 如果你以前用qXML, q且
弄不清楚该用元素q是该用属? 你不用感到孤? 别h一L涂着呢。没人真的搞得清
楚。这个选择与其说是Z技术的理由, q不如说是闭着眼瞎摸。从概念上来? 属性也
是一U元? M属性能做的, 元素一样做得到。XML引入属性的理由, 其实是Z?br>XML写法不那么冗ѝ比如我们看个例?
<copy>
<toDir>../new/dir</toDir>
<fileset>
<dir>src_dir</dir>
</fileset>
</copy>
两下比较, 内容的信息量完全一? 用属性可以减打字数量。如果XML没有属性的?
光是打字够把h搞疯掉?br>
说完了属性的问题, 我们再来看一看s表达式。之所以绕q么个弯, 是因为s表达式没有属
性的概念。因为s表达式非常简l? Ҏ没有必要引入属性。我们在把XML转换成s表达?br>的时? 心里应该Cq一炏V看个例? 上面的代码译成s表达式是q样?
(copy
(todir "../new/dir")
(fileset (dir "src_dir")))
仔细看看q个例子, 差别在哪? 括h成了圆括? 每个元素原来是有一Ҏh?br>包围? 现在取消了后一?是带斜杠的那个)括号标记。表C元素的l束只需要一?)"
可以了。不? 差别是q些。这两种表达方式的{? 非常自然, 也非常简单。s?br>辑ּ打v字来, 也省事得多。第一ơ看s表达?Lisp)? 括号很烦人是? 现在我们?br>白了背后的道? 一下子变得容易多了。至? 比XML要好的多。用s表达式写代码, ?br>单是实用, 而且也很让h愉快。s表达式具有XML的一切好? q些好处是我们刚刚探讨过
的。现在我们看看更加Lisp风格的task例子:
(task (name "Test")
(echo (message "Hellow World!")))
(Test)
用Lisp的行话来? s表达式称(list)。对于上面的例子, 如果我们写的时候不加换
? 用逗号来代替空? 那么q个表达式看h非常像一个元素列? 其中又嵌套着?br>他标记?br>
(task, (name, "test"), (echo, (message, "Hello World!")))
XML自然也可以用q样的风格来写。当然上面这句ƈ不是一般意义上的元素表。它实际?br>是一个树。这和XML的作用是一L。称它ؓ列表, 希望你不会感到迷? 因ؓ嵌套表和
树实际上是一码事。Lisp的字面意思就是表处理(list processing), 其实也可以称为树
处理, q和处理XML节点没有什么不同?br>
l受q一番折以? 现在我们l于相当接近Lisp? Lisp的括L秘本质(像许多
Lisp狂热分子认ؓ?逐渐昄出来。现在我们l研I其他内宏V?br>
重新审视C语言的宏
Cq里, 对XML的讨Z大概都听累了, 我都讲篏了。我们先停一? 把树, s表达?
Antq些东西先放一? 我们来说说C的预处理器。一定有人问? 我们的话题和C有什?br>关系? 我们已经知道了很多关于元~程的事? 也探讨过专门写代码的代码。理解这问题
有一定难? 因ؓ相关讨论文章所使用的编E语a, 都是你们不熟悉的。但是如果只论概
늚? q对要单一些。我怿, 如果以C语言做例子来讨论元编E? 理解h一?br>会容易得多。好, 我们接着看?br>
一个问题是, Z么要用代码来写代码呢? 在实际的~程? 怎样做到q一点呢? 到底?br>~程是什么意? 你大概已l听说过q些问题的答? 但是q不懂得其中~由。ؓ了揭C?br>背后的真? 我们来看一下一个简单的数据库查询问题。这U题目我们都做过。比方说,
直接在程序码里到处写SQL语句来修改表(table)里的数据, 写多了就非常烦h。即便用
C#3.0的LINQ, 仍然不减其痛苦。写一个完整的SQL查询(管语法很优?来修Ҏ人的?br>址, 或者查找某人的名字, l对是g令程序员倍感乏味的事? 那么我们该怎样来解册
个问? {案是: 使用数据讉K层?
概念挺简? 其要Ҏ把数据访问的内容(臛_是那些比较琐的部分)抽象出来, 用类?br>映射数据库的? 然后用访问对象属性访问器(accessor)的办法来间接实现查询。这样就
极大地简化了开发工作量。我们用讉K对象的方?或者属性赋? q要视你选用的语a
而定)来代替写SQL查询语句。凡是用q这U方法的? 都知道这很节省时间。当? 如果
你要亲自写这样一个抽象层, 那可是要花非常多的时间的--你要写一l类来映表, 把属
性访问{换ؓSQL查询, q个zȝ当耗费_֊。用手工来做昄是很不明智的。但是一?br>你有了方案和模板, 实际上就没有多少东西需要思考的。你只需要按照同L模板一ơ又
一ơ重复编写相g码就可以了。事实上很多人已l发C更好的方? 有一些工具可?br>帮助你连接数据库, 抓取数据库结构定?schema), 按照预定义的或者用户定制的模板?br>自动~写代码?br>
如果你用q这U工? 你肯定会对它的神奇效果深为折服。往往只需要鼠标点L? ?br>可以q接到数据库, 产生数据讉K源码, 然后把文件加入到你的工程里面, 十几分钟的工
? 按照往常手工方式来作的? 也许需要数百个时人工(man-hours)才能完成。可?
如果你的数据库结构定义后来改变了怎么? 那样的话, 你只需把这个过E重复一遍就?br>以了。甚x一些工兯自动完成q项变动工作。你只要把它作ؓ工程构造的一部分, ?br>ơ编译工E的时? 数据库部分也会自动地重新构造。这真的太棒了。你要做的事情基?br>上减C0。如果数据库l构定义发生了改? q在~译时自动更C数据讉K层的代码,
那么E序中Q何用过时的旧代码的地方, 都会引发~译错误?br>
数据讉K层是个很好的例子, q样的例子还有好多。从GUIh代码, WEB代码, COM?br>CORBA存根, 以及MFC和ATL{等。在q些地方, 都是有好多相g码多ơ重复。既然这?br>代码有可能自动编? 而程序员旉又远q比CPU旉昂贵, 当然׃生了好多工具来自
动生成样板代码。这些工L本质是什么呢? 它们实际上就是制造程序的E序。它们有一
个神U的名字, 叫做元编E。所谓元~程的本? 是如此?br>
元编E本来可以用到无数多的地? 但实际上使用的次数却没有那么多。归根结? 我们
心里q是在盘? 假设重复代码用拷贝粘贴的? 大概要重?,7? 对于q样的工作量,
值得专门建立一套生成工具吗? 当然不值得。数据访问层和COM存根往往需要重用数百次,
甚至上千? 所以用工具生成是最好的办法。而那些仅仅是重复几次十几ơ的代码, 是没
有必要专门做工具的。不必要的时候也d发代码生成工? 那就昄q度估计了代码生
成的好处。当? 如果创徏q类工具_单的? q是应当量多用, 因ؓq样做必?br>会节省时间。现在来看一下有没有合理的办法来辑ֈq个目的?br>
现在, C预处理器要派上用Z。我们都用过C/C++的预处理? 我们用它执行单的~译
指o, 来生简单的代码变换(比方? 讄调试代码开?, 看一个例?
#define triple(X) X+X+X
q一行的作用是什? q是一个简单的预编译指? 它把E序中的triple(X)替换UCؓ
X+X+X。例? 把所有的triple(5)都换?+5+5, 然后再交l编译器~译。这是一个简
单的代码生成的例子。要是C的预处理器再强大一? 要是能够允许q接数据? 要是?br>多一些其他简单的机制, 我们可以在我们E序的内部开发自q数据讉K层。下面这?br>例子, 是一个假想的对C宏的扩展:
#get-db-schema("127.0.0.1")
#iterate-through-tables
#for-each-table
class #table-name
{
};
#end-for-each
我们q接数据库结构定? 遍历数据? 然后Ҏ个表创徏一个类, 只消几行代码完?br>了这个工作。这hơ编译工E的时? q些c都会根据数据库的定义同步更新。显而易
? 我们不费吹灰之力在E序内部建立了一个完整的数据讉K? Ҏ用不着M外部
工具。当然这U作法有一个缺? 那就是我们得学习一套新?~译时语a", 另一个缺?br>是Ҏ不存在这么一个高U版的C预处理器。需要做复杂代码生成的时? q个语言(?br>者注: q里指预处理指o, 即作者所说的"~译时语a")本n也一定会变得相当复杂。它
必须支持_多的库和语言l构。比如说我们惌生成的代码要依赖某些ftp服务器上?br>文g, 预处理器得支持ftp讉K, 仅仅因ؓq个d而不得不创造和学习一门新的语a,
真是有点让h恶心(事实上已l存在着有此能力的语a, q样做就更显荒谬)。我们不妨再
灉|一? Z么不直接?C/C++自己作ؓ自己的预处理语言? q样子的? 我们?br>以发挥语a的强大能? 要学的新东西也只不过是几个简单的指示?, q些指示字用?br>区别~译时代码和q行时代码?br>
<%
cout<<"Enter a number: ";
cin>>n;
%>
for(int i=0;i< <% n %>;i++)
{
cout<<"hello"<<endl;
}
你明白了? ?lt;%?>标记之间的代码是在编译时q行? 标记之外的其他代码都是普?br>代码。编译程序时, pȝ会提CZ输入一个数, q个数在后面的@环中会用到。而for?br>环的代码会被~译。假定你在编译时输入5, for循环的代码将会是:
for(int i=0;i<5; i++)
{
cout<<"hello"<<endl;
}
又简单又有效? 也不需要另外的预处理语a。我们可以在~译时就充分发挥宿主语言(
此处是C/C++)的强大能? 我们可以很容易地在编译时q接数据? 建立数据讉K? ?br>像JSP或者ASP创徏|页那样。我们也用不着专门的窗口工h另外建立工程。我们可以在
代码中立卛_入必要的工具。我们也用不着虑建立q种工具是不是值得, 因ؓq太Ҏ
? 太简单了。这样子不知可以节省多少旉啊?br>
你好, Lisp
到此Mؓ? 我们所知的关于Lisp的指C可以ȝZ句话: Lisp是一个可执行的语法更
优美的XML, 但我们还没有说Lisp是怎样做到q一点的, 现在开始补上这个话题?
Lisp有丰富的内置数据cd, 其中的整数和字符串和其他语言没什么分别。像71或?br>"hello"q样的? 含义也和C++或者Javaq样的语a大体相同。真正有意思的三种cd?br>W号(symbol), 表和函数。这一章的剩余部分, 我都会用来介l这几种cd, q要介绍
Lisp环境是怎样~译和运行源码的。这个过E用Lisp的术语来说通常叫做求倹{通读q一
节内? 对于透彻理解元编E的真正潜力, 以及代码和数据的同一? 和面向领域语a?br>观念, 都极光要。万勿等闲视之。我会尽量讲得生动有一? 也希望你能获得一?br>启发。那? 我们先讲W号?br>
大体? W号相当于C++或Java语言中的标志W? 它的名字可以用来讉K变量?例如
currentTime, arrayCount, n, {等), 差别在于, Lisp中的W号更加基本。在C++?br>Java里面, 变量名只能用字母和下划线的组? 而Lisp的符号则非常有包Ҏ? 比如, ?br>?+)是一个合法的W号, 其他的像-, =, hello-world, *{等都可以是W号名。符?br>名的命名规则可以在网上查到。你可以l这些符号Q意赋? 我们q里先用伪码来说明这
一炏V假定函数set是给变量赋?像{号=在C++和Java里的作用), 下面是我们的例子:
set(test, 5) // W号test的gؓ5
set(=, 5) // W号=的gؓ5
set(test, "hello") // W号test的gؓ字符?hello"
set(test, =) // 此时W号=的gؓ5, 所以test的也?
set(*, "hello") // W号*的gؓ"hello"
好像有什么不对的地方? 假定我们?赋给整数或者字W串? 那做乘法时怎么? 不管
怎么? *L乘法呀? {案单极了。Lisp中函数的角色十分Ҏ, 函数也是一U数?br>cd, 像整数和字W串一? 因此可以把它赋值给W号。乘法函数Lisp的内|函? ?br>认赋l?, 你可以把其他函数赋值给*, 那样*׃代表乘法了。你也可以把q函数的值存
到另外的变量里。我们再用伪码来说明一?
*(3,4) // 3?, l果?2
set(temp, *) // ?的? 也就是乘法函? 赋值给temp
set(*, 3) // ?赋予*
*(3,4) // 错误的表辑ּ, *不再是乘? 而是数?
temp(3,4) // temp是乘法函? 所以此表达式的gؓ3?{于12
set(*, temp) // 再次把乘法函数赋?
*(3,4) // 3?{于12
再古怪一? 把减LDl加?
set(+, -) // 减号(-)是内|的减法函数
+(5, 4) // 加号(+)现在是代表减法函? l果??{于1
q只是D例子, 我还没有详细讲函数。Lisp中的函数是一U数据类? 和整? 字符?
W号{等一栗一个函数ƈ不必然有一个名? q和C++或者Java语言的情形很不相同?br>在这里函数自׃表自己。事实上它是一个指向代码块的指? 附带有一些其他信??br>如一l参数变?。只有在把函数赋予其他符h, 它才h了名? 像把一个数值或
字符串赋予变量一L道理。你可以用一个内|的专门用于创徏函数的函数来创徏函数,
然后把它赋值给W号fn, 用伪码来表示是:
fn [a]
{
return *(a, 2);
}
q段代码q回一个具有一个参数的函数, 函数的功能是计算参数?的结果。这个函数还
没有名字, 你可以把此函数赋值给别的W号:
set(times-two, fn [a] {return *(a, 2)})
我们现在可以q样调用q个函数:
time-two(5) // q回10
我们先蟩q符号和函数, 讲一讲表。什么是? 你也许已l听q好多相关的说法。表, 一
a以蔽? 是把类似XML那样的数据块, 用s表达式来表示。表用一Ҏh? 表中?br>素以I格分隔, 表可以嵌套。例?q回我们用真正的Lisp语法, 注意用分可C注?:
() ; I
(1) ; 含一个元素的?br> (1 "test") ; 两元素表, 一个元素是整数1, 另一个是字符?br> (test "hello") ; 两元素表, 一个元素是W号, 另一个是字符?br> (test (1 2) "hello") ; 三元素表, 一个符号test, 一个含有两个元???br> ; ? 最后一个元素是字符?br>
当Lisppȝ遇到q样的表? 它所做的, 和Ant处理XML数据所做的, 非常怼, 那就是试
图执行它们。其? Lisp源码是特定的一U表, 好比Ant源码是一U特定的XML一栗?br>Lisp执行表的序是这L, 表的W一个元素当作函? 其他元素当作函数的参数。如?br>其中某个参数也是? 那就按照同样的原则对q个表求? l果再传递给最初的函数作ؓ
参数。这是基本原则。我们看一下真正的代码:
(* 3 4) ; 相当于前面列举过的伪?(3,4), 卌??
(times-two 5) ; q回10, times-two按照前面的定义是求参数的2?br> (3 4) ; 错误, 3不是函数
(time-two) ; 错误, times-two要求一个参?br> (times-two 3 4) ; 错误, times-two只要求一个参?br> (set + -) ; 把减法函数赋予符?
(+ 5 4) ; 依据上一句的l果, 此时+表示减法, 所以返?
(* 3 (+ 2 2)) ; 2+2的结果是4, 再乘3, l果?2
上述的例子中, 所有的表都是当作代码来处理的。怎样把表当作数据来处理呢? 同样?
设想一? Ant是把XML数据当作自己的参数。在Lisp? 我们l表加一个前~'来表C数
据?br>
(set test '(1 2)) ; test的gؓ两元素表
(set test (1 2)) ; 错误, 1不是函数
(set test '(* 3 4)) ; test的值是三元素表, 三个元素分别?, 3, 4
我们可以用一个内|的函数head来返回表的第一个元? tail函数来返回剩余元素组成的
表?br>
(head '(* 3 4)) ; q回W号*
(tail '(* 3 4)) ; q回?3 4)
(head (tal '(* 3 4))) ; q回3
(head test) ; q回*
你可以把Lisp的内|函数想像成Ant的Q务。差别在? 我们不用在另外的语言中扩?br>Lisp(虽然完全可以做得?, 我们可以用Lisp自己来扩展自? 像上面丄times-two
函数的例子。Lisp的内|函数集十分_, 只包含了十分必要的部分。剩下的函数都是?br>为标准库来实现的?br>
Lisp?br>
我们已经看到, 元编E在一个类似jsp的模板引擎方面的应用。我们通过单的字符串处
理来生成代码。但是我们可以做的更好。我们先提一个问? 怎样写一个工? 通过查找
目录l构中的源文件来自动生成Ant脚本?br>
用字W串处理的方式生成Ant脚本是一U简单的方式。当? q有一U更加抽? 表达?br>力更? 扩展性更好的方式, 是利用XML库在内存中直接生成XML节点, q样的话内存?br>的节点就可以自动序列化成为字W串。不仅如? 我们的工兯可以分析q些节点, 对已
有的XML文g做变换。通过直接处理XML节点。我们可以超字W串处理, 使用更高层次?br>概念, 因此我们的工作就会做的更快更好?br>
我们当然可以直接用Ant自n来处理XML变换和制作代码生成工兗或者我们也可以用Lisp
来做q项工作。正像我们以前所知的, 表是Lisp内置的数据结? Lisp含有大量的工h
快速有效的操作?head和tail是最单的两个)。而且, Lisp没有语义U束, 你可以构?br>M数据l构, 只要你原意?br>
Lisp通过?macro)来做元编E。我们写一l宏来把d列表(to-do list)转换Z用领
域语a?br>
回想一下上面to-do list的例? 其XML的数据格式是q样?
<todo name = "housework">
<item priority = "high">Clean the hose</item>
<item priority = "medium">Wash the dishes</item>
<item priority = "medium">Buy more soap</item>
</todo>
相应的s表达式是q样?
(todo "housework"
(item (priority high) "Clean the house")
(item (priority medium) "Wash the dishes")
(item (priority medium) "Buy more soap"))
假设我们要写一个Q务表的管理程? 把Q务表数据存到一l文仉, 当程序启动时, ?br>文gdq些数据q显C给用户。在别的语言?比如说Java), q个d该怎么? 我们
会解析XML文g, 从中得出d表数? 然后写代码遍历XML? 再{换ؓJava的数据结?br>(老实? 在Java里解析XML真不是gL的事?, 最后再把数据展C给用户。现在如?br>用Lisp, 该怎么?
假定要用同样思\的化, 我们大概会用Lisp库来解析XML。XMLҎ们来说就是一个Lisp
的表(s表达?, 我们可以遍历q个? 然后把相x据提交给用户。可? 既然我们?br>Lisp, 根本没有必要再用XML格式保存数据, 直接用s表达式就好了, q样没有必要做
转换了。我们也用不着专门的解析库, Lisp可以直接在内存里处理s表达式。注? Lisp
~译器和.net~译器一? 对LispE序来说, 在运行时L随时可用的?br>
但是q有更好的办法。我们甚至不用写表达式来存储数据, 我们可以写宏, 把数据当作代
码来处理。那该怎么做呢? 真的单。回想一? Lisp的函数调用格?
(function-name arg1 arg2 arg3)
其中每个参数都是s表达? 求g? 传递给函数。如果我们用(+ 4 5)来代替arg1,
那么, E序会先求出l果, 是9, 然后?传递给函数。宏的工作方式和函数cM。主?br>的差别是, 宏的参数在代入时不求倹{?br>
(macro-name (+ 4 5))
q里, (+ 4 5)作ؓ一个表传递给? 然后宏就可以L处理q个? 当然也可以对它求
倹{宏的返回值是一个表, 然后有程序作Z码来执行。宏所占的位置, p替换?br>l果代码。我们可以定义一个宏把数据替换ؓL代码, 比方? 替换为显C数据给用户
的代码?br>
q和元编E? 以及我们要做的Q务表E序有什么关pd? 实际? ~译器会替我们工?
调用相应的宏。我们所要做? 仅仅是创Z个把数据转换为适当代码的宏?br>
例如, 上面曄过的C的求三次方的? 用Lisp来写是这样子:
(defmacro triple (x)
`(+ ~x ~x ~x))
(译注: 在Common Lisp? 此处的单引号应当是反单引? 意思是对表不求? 但可以对
表中某元素求? 记号~表示对元素x求? q个求D号在Common Lisp中应当是逗号?br>反单引号和单引号的区别是, 单引h识的? 其中的元素都不求倹{这里作者所用的?br>h自己发明的一ULisp方言Blaise, 和common lisp略有不同, 事实? 发明方言?br>lisp高手独有的乐? 很多狂热分子都热衯样做。比如Paul Graham发明了ARC, 许多
记号比传l的Lispz得? 昑־比较C)
单引L用处是禁止对表求倹{每ơ程序中出现triple的时?
(triple 4)
都会被替换成:
(+ 4 4 4)
我们可以ZQ务表E序写一个宏, 把Q务数据{换ؓ可执行码, 然后执行。假定我们的?br>出是在控制台:
(defmacro item (priority note)
`(block
(print stdout tab "Prority: " ~(head (tail priority)) endl)
(print stdout tab "Note: " ~note endl endl)))
我们创造了一个非常小的有限的语言来管理嵌在Lisp中的d表。这个语a只用来解决特
定领域的问题, 通常UC为DSLs(特定领域语言, 或专用领域语a)?br>
特定领域语言
本文谈到了两个特定领域语a, 一个是Ant, 处理软g构造。一个是没v名字? 用于?br>理Q务表。两者的差别在于, Ant是用XML, XML解析? 以及Java语言合在一h造出?br>的。而我们的q你语言则完全内嵌在Lisp? 只消几分钟就做出来了?br>
我们已经说过了DSL的好? q也是Ant用XML而不直接用Java的原因。如果用Lisp,
我们可以L创徏DSL, 只要我们需要。我们可以创建用于网站程序的DSL, 可以写多用户
游戏, 做固定收益N?fixed income trade), 解决蛋白质折叠问? 处理事务问题, {?br>{。我们可以把q些叠放在一? 造出一个语a, 专门解决Z|络的N易程? 既有|?br>l语a的优? 又有贸易语言的好处。每天我们都会收莯U方法带l我们的益处, q远
过Ant所能给予我们的?br>
用DSL解决问题, 做出的程序精, 易于l护, 富有Ҏ。在Java里面, 我们可以用类?br>处理问题。这两种Ҏ的差别在? Lisp使我们达C一个更高层ơ的抽象, 我们不再?br>语言解析器本w的限制, 比较一下用Java库直接写的构造脚本和用Ant写的构造脚本其?br>的差别。同L, 比较一下你以前所做的工作, 你就会明白Lisp带来的好处?br>
接下?br>
学习Lisp像战争中争夺山头。尽在电脑U学领域, Lisp已经是一门古老的语言, ?br>到现在仍然很有人真的明白该怎样l初学者讲授Lisp。尽Lisp老手们尽了很大努?
今天新手学习Lisp仍然是困N重。好在现在事情正在发生变? Lisp的资源正在迅速增
? 随着旉推移, Lisp会来受x?br>
Lisp使h越q_, 走到前沿。学会Lisp意味着你能扑ֈ更好的工? 因ؓ聪明的雇M
被你与众不同的洞察力所打动。学会Lisp也可能意味着明天你可能会被解? 因ؓ你L
, 如果公司所有Y仉用Lisp? 公司会如何卓越, 而这些话你的同事会听烦的?br>Lisp值得努力学习? 那些已经学会Lisp的h都说值得, 当然, q取决于你的判断?br>
你的看法?
q篇文章写写停停, 用了几个月才最l完成。如果你觉得有趣, 或者有什么问? 意见?br>, L我发邮gcoffeemug@gmail.com, 我会很高兴收C的反馈?
]]>
W一ơ担赯?br>W一ơ怕自׃?br>W一ơ有那么一点理解到底什么是ȝ
q一天仅作纪?)