转载 – 若水斋 https://blog.werner.wiki Try harder Sat, 10 Nov 2018 09:54:39 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.8.3 https://blog.werner.wiki/wp-content/uploads/2018/11/cropped-ql1-1-32x32.jpg 转载 – 若水斋 https://blog.werner.wiki 32 32 [转]青年在选择职业时的考虑 https://blog.werner.wiki/reflections-of-youth-on-choice-profession/ https://blog.werner.wiki/reflections-of-youth-on-choice-profession/#respond Fri, 01 Sep 2017 09:53:12 +0000 http://blog.werner.wiki/?p=326

作者:卡尔·亨利希·马克思

自然本身给动物规定了它应该遵循的活动范围,动物也就安分地在这个范围内活动,不试图越出这个范围,甚至不考虑有其他什么范围的存在。神也给人指定了共同的目标──使人类和他自己趋于高尚,但是,神要人自己去寻找可以达到这个目标的手段;神让人在社会上选择一个最适合于他、最能使他和社会都得到提高的地位。

能有这样的选择是人比其他生物远为优越的地方,但是这同时也是可能毁灭人的一生、破坏他的一切计划并使他陷于不幸的行为。因此,认真地考虑这种选择──这无疑是开始走上生活道路而又不愿拿自己最重要的事业去碰运气的青年的首要责任。

每个人眼前都有一个目标,这个目标至少在他本人看来是伟大的,而且如果最深刻的信念,即内心深处的声音,认为这个目标是伟大的,那他实际上也是伟大的,因为神决不会使世人完全没有引导;神总是轻声而坚定地作启示。

但是,这声音很容易被淹没;我们认为是灵感的东西可能须臾而生,同样可能须臾而逝。也许,我们的幻想油然而生,我们的感情激动起来,我们的眼前浮想联翩,我们狂热地追求我们以为是神本身给我们指出的目标;但是,我们梦寐以求的东西很快就使我们厌恶──于是我们的整个存在也就毁灭了。

因此,我们应当认真考虑:所选择的职业是不是真正使我们受到鼓舞?我们的内心是不是同意?我们受到的鼓舞是不是一种迷误?我们认为是神的召唤的东西是不是一种自欺?但是,不找出鼓舞的来源本身,我们怎么能认清这些呢?

伟大的东西是光辉的,光辉则引起虚荣心,而虚荣心容易给人鼓舞或者是一种我们觉得是鼓舞的东西;但是,被名利弄得鬼迷心窍的人,理智已无法支配他,于是他一头栽进那不可抗拒的欲念驱使他去的地方;他已经不再自己选择他在社会上的地位,而听任偶然机会和幻想去决定它。

我们的使命绝不是求得一个最足以炫耀的职业,因为它不是那种使我们长期从事而始终不会感到厌倦、始终不会松动、始终不会情绪低落的职业,相反,我们很快就会觉得,我们的愿望没有得到满足,我们理想没有实现,我们就将怨天尤人。

但是,不只是虚荣心能够引起对这种或那种职业突然的热情。也许,我们自己也会用幻想把这种职业美化,把它美化成人生所能提供的至高无上的东西。我们没有仔细分析它,没有衡量它的全部份量,即它让我们承担的重大责任;我们只是从远处观察它,然而从远处观察是靠不住的。

在这里,我们自己的理智不能给我们充当顾问,因为它既不是依靠经验,也不是依靠深入的观察,而是被感情欺骗,受幻想蒙蔽。然而,我们的目光应该投向哪里呢?在我们丧失理智的地方,谁来支持我们呢?

是我们的父母, 他们走过了漫长的生活道路, 饱尝了人世的辛酸。──我们的心这样提醒我们。

如果我们通过冷静的研究,认清所选择的职业的全部份量,了解它的困难以后,我们仍然对它充满热情,我们仍然爱它。觉得自己适合它,那时我们就应该选择它,那时我们既不会受热情的欺骗,也不会仓促从事。

但是,我们并不能总是能够选择我们自认为适合的职业;我们在社会上的关系,还在我们有能力对它们起决定性影响以前就已经在某种程度上开始确立了。

我们的体质常常威胁我们,可是任何人也不敢藐视它的权利。

诚然,我们能够超越体质的限制,但这么一来,我们也就垮得更快;在这种情况下,我们就是冒险把大厦筑在松软的废墟上,我们的一生也就变成一场精神原则和肉体原则之间的不幸的斗争。但是,一个不能克服自身相互斗争的因素的人,又怎能抗拒生活的猛烈冲击,怎能安静地从事活动呢?然而只有从安静中才能产生伟大壮丽的事业,安静是唯一生长出成熟果实的土壤。

尽管我们由于体质不适合我们的职业,不能持久地工作,而且工作起来也很少乐趣,但是,为了恪尽职守而牺牲自己幸福的思想激励着我们不顾体弱去努力工作。如果我们选择了力不能胜任的职业,那么我们决不能把它做好,我们很快就会自愧无能,并对自己说,我们是无用的人,是不能完成自己使命的社会成员。由此产生的必然结果就是妄自菲薄。还有比这更痛苦的感情吗?还有比这更难于靠外界的赐予来补偿的感情吗?妄自菲薄是一条毒蛇,它永远啮噬着我们心灵,吮吸着其中滋润生命的血液,注入厌世和绝望的毒液。

如果我们错误地估计了自己的能力,以为能够胜任经过周密考虑而选定的职业,那么这种错误将使我们受到惩罚。即使不受到外界指责,我们也会感到比外界指责更为可怕的痛苦。

如果我们把这一切都考虑过了,如果我们生活的条件容许我们选择任何一种职业;那么我们就可以选择一种能使我们最有尊严的职业;选择一种建立在我们深信其正确的思想上的职业;选择一种能给我们提供广阔场所来为人类进行活动、接近共同目标(对于这个目标来说,一切职业只不过是手段)即完美境地的职业。

尊严就是最能使人高尚起来、使他的活动和他的一切努力具有崇高品质的东西,就是使他无可非议、受到众人钦佩并高出于众人之上的东西。

但是,能给人以尊严的只有这样的职业,在从事这种职业时我们不是作为奴隶般的工具,而是在自己的领域内独立地进行创造;这种职业不需要有不体面的行动(哪怕只是表面上不体面的行动),甚至最优秀的人物也会怀着崇高的自豪感去从事它。最合乎这些要求的职业,并不一定是最高的职业,但总是最可取的职业。

但是,正如有失尊严的职业会贬低我们一样,那种建立在我们后来认为是错误的思想上的职业也一定使我们感到压抑。

这里,我们除了自我欺骗,别无解救办法,而以自我欺骗来解救又是多么糟糕!

那些不是干预生活本身,而是从事抽象真理研究的职业,对于还没有坚定的原则和牢固、不可动摇的信念的青年是最危险的。同时,如果这些职业在我们心里深深地扎下了根,如果我们能够为它们的支配思想牺牲生命、竭尽全力,这些职业看来似乎还是最高尚的。

这些职业能够使才能适合的人幸福,但也必定使那些不经考虑、凭一时冲动就仓促从事的人毁灭。

相反,重视作为我们职业的基础的思想,会使我们在社会上占有较高的地位,提高我们本身的尊严,使我们的行为不可动摇。

一个选择了自己所珍视的职业的人,一想到他可能不称职时就会战战兢兢──这种人单是因为他在社会上所居地位是高尚的,他也就会使自己的行为保持高尚。

在选择职业时,我们应该遵循的主要指针是人类的幸福和我们自身的完美。不应认为,这两种利益是敌对的,互相冲突的,一种利益必须消灭另一种的;人类的天性本来就是这样的:人们只有为同时代人的完美、为他们的幸福而工作,才能使自己也达到完美。

如果一个人只为自己劳动,他也许能够成为著名的学者、大哲人、卓越诗人,然而他永远不能成为完美无疵的伟大人物。

历史承认那些为共同目标劳动因而自己变得高尚的人是伟大人物;经验赞美那些为大多数人带来幸福的人是最幸福的人;宗教本身也教诲我们,人人敬仰的理想人物,就曾为人类牺牲了自己──有谁敢否定这类教诲呢?

如果我们选择了最能为人类福利而劳动的职业,那么,重担就不能把我们压倒,因为这是为大家而献身;那时我们所感到的就不是可怜的、有限的、自私的乐趣,我们的幸福将属于千百万人,我们的事业将默默地、但是永恒发挥作用地存在下去,面对我们的骨灰,高尚的人们将洒下热泪。

卡尔·亨利希·马克思于1835年8月12日

]]>
https://blog.werner.wiki/reflections-of-youth-on-choice-profession/feed/ 0
[转]人的正确思想是从哪里来的? https://blog.werner.wiki/where-correct-thought-from/ https://blog.werner.wiki/where-correct-thought-from/#respond Thu, 24 Aug 2017 09:51:01 +0000 http://blog.werner.wiki/?p=323

作者:毛泽东

人的正确思想是从哪里来的?是从天上掉下来的吗?不是。是自己头脑里固有的吗?不是。人的正确思想,只能从社会实践中来,只能从社会的生产斗争、阶级斗争和科学实验这三项实践中来。

人们的社会存在,决定人们的思想。而代表先进阶级的正确思想,一旦被群众掌握,就会变成改造社会、改造世界的物质力量。人们在社会实践中从事各项斗争,有了丰富的经验,有成功的,有失败的。无数客观外界的现象通过人的眼、耳、鼻、舌、身这五个官能反映到自己的头脑中来,开始是感性认识。这种感性认识的材料积累多了,就会产生一个飞跃,变成了理性认识,这就是思想。这是一个认识过程。这是整个认识过程的第一个阶段,即由客观物质到主观精神的阶段,由存在到思想的阶段。

这时候的精神、思想(包括理论、政策、计划、办法)是否正确地反映了客观外界的规律,还是没有证明的,还不能确定是否正确,然后又有认识过程的第二个阶段,即由精神到物质的阶段,由思想到存在的阶段,这就是把第一个阶段得到的认识放到社会实践中去,看这些理论、政策、计划、办法等等是否能得到预期的成功。一般的说来,成功了的就是正确的,失败了的就是错误的,特别是人类对自然界的斗争是如此。在社会斗争中,代表先进阶级的势力,有时候有些失败,并不是因为思想不正确,而是因为在斗争力量的对比上,先进势力这一方,暂时还不如反动势力那一方,所以暂时失败了,但是以后总有一天会要成功的。人们的认识经过实践的考验,又会产生一个飞跃。这次飞跃,比起前一次飞跃来,意义更加伟大。因为只有这一次飞跃,才能证明认识的第一次飞跃,即从客观外界的反映过程中得到的思想、理论、政策、计划、办法等等,究竟是正确的还是错误的,此外再无别的检验真理的办法。

而无产阶级认识世界的目的,只是为了改造世界,此外再无别的目的。一个正确的认识,往往需要经过由物质到精神,由精神到物质,即由实践到认识,由认识到实践这样多次的反复,才能够完成。这就是马克思主义的认识论,就是辩证唯物论的认识论。现在我们的同志中,有很多人还不懂得这个认识论的道理。问他的思想、意见、政策、方法、计划、结论、滔滔不绝的演说、大块的文章,是从哪里得来的,他觉得是个怪问题,回答不出来。对于物质可以变成精神,精神可以变成物质这样日常生活中常见的飞跃现象,也觉得不可理解。因此,对我们的同志,应当进行辩证唯物论的认识论的教育,以便端正思想,善于调查研究,总结经验,克服困难,少犯错误,做好工作,努力奋斗,建设一个社会主义的伟大强国,并且帮助世界被压迫被剥削的广大人民,完成我们应当担负的国际主义的伟大义务。

]]>
https://blog.werner.wiki/where-correct-thought-from/feed/ 0
[转]夜吟 https://blog.werner.wiki/evening-poetry/ https://blog.werner.wiki/evening-poetry/#respond Sun, 21 May 2017 06:41:10 +0000 http://blog.werner.wiki/?p=213

作者:陆游

六十余年妄学诗,功夫深处独心知。
夜来一笑寒灯下,始是金丹换骨时。

]]>
https://blog.werner.wiki/evening-poetry/feed/ 0
[转]国学入门书要目及其读法 https://blog.werner.wiki/the-entry-and-reading-of-the-sinology-book/ https://blog.werner.wiki/the-entry-and-reading-of-the-sinology-book/#respond Wed, 29 Mar 2017 05:40:27 +0000 http://blog.werner.wiki/?p=182

作者:梁启超

两月前清华周刊记者以此题相属,蹉跎久未报命。顷独居翠微山中,行箧无一书,而记者督责甚急,乃竭三日之力,专凭忆想所及草斯篇,漏略自所不免,且容有并书名篇名亦忆错误者,他日更当补正也。

中华民国十二年四月二十六日启超作于碧摩岩翠山房

(甲)修养应用及思想史关系书类

《论语》 《孟子》

《论语》为二千来国人思想之总源泉,《孟子》自宋以后势力亦与相埒,此二书可谓国人内的外的生活之支配者,故吾希望学者熟读成诵,即不能,亦须翻阅多次,务略举其辞,或摘记其身心践履之言以资修养。

《论语》、《孟子》之文,并不艰深,宜专读正文,有不解处,方看注释。注释之书,朱熹《四书集注》,为其生平极矜慎之作,可读,但其中有随入宋儒理障处,宜分别观之。清儒注本,《论语》则有戴望《论语》注,《孟子》则有焦循《孟子》正义最善。戴氏服膺颜习斋之学,最重实践,所注似近孔门真际,其训诂亦多较朱注为优,其书简洁易读。焦氏服膺戴东原之学,其孟子正义在清儒诸经新疏中为最佳本,但文颇繁,宜备置案头,遇不解时,或有所感时,则取供参考。

戴震《孟子字义疏证》,乃戴氏一家哲学,并非专为注释《孟子》而作,但其书极精辟,学者终需一读,最好是于读《孟子》时并读之,既知戴学纲领,亦可以助读《孟子》之兴味。

焦循《论语通释》,乃摹仿《孟子字义疏证》而作,将全部《论语》拆散,标准重要诸义,如言仁,言忠恕……等,列为若干目,通观而总诠之可称治《论语》之一良法且可应用其法以治他书。

右两书篇页皆甚少,易读。

陈沣《东塾读书记》中读《孟子》之卷,取《孟子》学说分项爬疏,最为精切,其书不过二三十页,宜一读以观前辈治学方法,且于修养亦有益。

《易经》

此书为孔子以前之哲学书,孔子为之注解,虽奥衍难究,然总须一读。吾希望学者将《系辞传》、《文言传》熟读成诵,其卦象传六十四条,则用别纸钞出,随时省览。

后世说《易》者言人人殊,为修养有益起见,则程颐之《程氏易传》差可读。

说《易》最近真者,吾独推焦循,其的著《雕菰楼易学》三书(《易通释》、《易图略》、《易章句》)皆称精诣,学者如欲深通此经,可取读之,否则可以不必。

《礼记》

此书战国及西汉之“儒家言”丛编,内中有极精纯者,亦有极破碎者,吾希望学者将《中庸》、《大学》、《礼运》、《乐记》四篇熟读成诵,《曲礼》、《王制》、《檀弓》、《礼器》、《学记》、《坊记》、《表记》、《缁衣》、《儒行》、《大傅》、《祭义》、《祭法》、《乡饮酒义》诸篇 ,多游览数次,且摘录其精要语。

若欲看注解,可看《十三经注疏》内郑注孔疏。《孝经》之性质与《礼记》同,可当《礼记》之一篇读。

《老子》

道家最精要之书,希望学者将此区区五千言熟读成诵。

注释书未有极当意者,专读白文自行寻索为妙。

《墨子》

孔墨在先秦时,两圣并称,故此书非读不可,除《备城门》以下各篇外,余篇皆宜精读。

注释书以孙诒让《墨子间诂》为最善,读《墨子》宜照读此本。

《经》上、下,《经说》上、下四篇,有张惠言《墨子经说解》及梁启超《墨经》两书可参观,但皆有未精惬处,《小取》篇有胡适新诂可参观。

梁启超《墨子学案》属通释体裁,可参观助兴味,但其书为临时讲义,殊未精审。

《庄子》

内篇七篇及杂篇中之《天下篇》最当精读。

注释有郭庆藩之《庄子集释》差可。

《荀子》

《解蔽》、《正名》、《天论》、《正论》、《性恶》、《礼论》、《乐论》诸篇最当精读,余亦须全部游览。

注释书王先谦《荀子注》甚善。

《慎文子》 《慎子》 《公孙龙子》

今存者皆非完书,但三子皆为先秦大哲,虽断简亦宜一读,篇帙甚少,不费力也。《分孙龙子》之真伪,尚有问题。

三书皆无善注,《尹文子》、《慎子》易解。

《韩非子》

法家言之精华,须全部游览(其特别应精读之诸篇,因手边无原书,?举恐遗漏,他日补列)。

注释书王先谦《韩非子集释》差可。

《管子》

战国末年人所集著者,性质颇杂驳,然古代各家学说存其是者颇多,宜一游览。

注释书戴望《管子校正》甚好。

《吕氏春秋》

此为中国最古之类书,先秦学说存其中者颇多,宜游览。

《淮南子》

此为秦汉间道家言荟萃之书,宜稍精读。

注释书闻有刘文典《淮南鸿烈集解》颇好。

《春秋繁露》

此为西汉儒家代表的著作,宜稍精读。

注释书有苏舆《春秋繁露义证》颇好。

康有为之《春秋董氏学》,为通释体裁,家参看。

《盐铁论》

此书为汉代儒家法家对于政治问题对垒抗辩之书,宜游览。

《论衡》

此书为汉代怀疑派哲学,宜游览。

《抱朴子》

此书为晋以后道家言代表作品,宜游览。

《列子》

晋人伪书,可作魏晋部玄学书读。

右所列为汉晋以前思想界之重要著作,六朝隋唐间思想界著光采者为佛学其书目当别述之。以下举宋以后学术之代表书,但为一般学者节啬精力计,不愿多举也。

《近思录》 朱熹著,江永注

读此书可见程朱一派之理学,其内容何如。

《朱子所谱·朱子论学要语》 王懋竑著

此书叙述朱学全面目最精要,有条理。

若欲研究程朱学派,宜读《二程遗书》及《朱子语类》,非专门斯业者可置之。

南宋时与朱学对峙者尚有李东莱之文献学一派,陈龙川、叶水心之功利主义一派,及陆象山之心学一派,欲知其详,宜读各人专集;若观大略,可求诸《宋元学案》中。

《传习录》 王守仁语徐爰、钱洪德等记

读此可知王学梗概。欲知其详,宜读《王文成公全书》。因阳明以知行合一为教,要合观学问事功,方能看出其全部人格,而其事功之经过,具见集中各文,故阳明集之重要,过于朱、陆诸集。

《明儒学案》 黄宗羲著

《宋元学案》 黄宗羲初稿,全祖望、王梓材两次续成

此二书为宋元明三朝理学之总记录,实为创作的学术史。《明儒学案》中姚江、江右、王门、泰州、东林、戢山诸案最精善。《宋元学案》中象山案最精善;横渠、二程、东莱、龙川、水心诸案亦好;晦翁案不甚好;百源(邵雄)、涑水(司马光)诸案,失之太繁,反不见其真相;末附荆公(王安石)新学略最坏,因有门户之见,故为排斥。欲知荆公学术,宜看《王临川集》。

此二书卷帙虽繁,吾总望学者择要游览,因其为六百年间学术之总汇,影响于近代甚深,且汇诸家为一编,读之不甚费力也。

清代学术史,可惜尚无此等佳著。唐鉴之《国朝案小识》,以清代最不振之程朱学派为立脚点,偏狭固陋,万不可读;江藩之《国朝汉学师承记》、《国朝宋学渊源记》,亦学案体裁,较好,但江氏常识亦凡庸,殊不能叙出各家独到之处,万不得已,姑以备参考而已。启超方有事于《清儒学案》,汗青尚无期也。

《日知录》、《亭林文集》 顾炎武著

顾亭林为清学开山第一人,其精力集注于《日知录》,宜一游览。读文集中各信札,可见其立身治学大概。

《明夷待访录》 黄宗羲著

黄梨洲为清初大师之一,其最大贡献在两学案,此小册可见其政治思想之大概。

《思问录》 王夫之著

王船山为清初大师之一,非通观全书,不能见其精深博大,但卷帙太繁,非别为系统的整理,则学者不能读,聊举此书发凡,实不足以代表其学问之全部也。

《颜氏学记》 戴望编

颜习斋为清初大师之一,戴氏所编学记,颇能传其真。徐世昌之《颜李学》亦可供参考,但其所集《习斋语要》、《恕谷(李塨)语要》将攻击宋儒语多不录,稍失其真。

顾、黄、王、颜四先生之学术,为学者所必须知,然其著述皆浩博,或散佚,不易寻?,启超行将为系统的整理记述,以饷学者。

《东原集》 戴震著

《雕菰楼集》 焦循著

戴东原、焦里堂为清代经师中有清深之哲学思想者,读其集可知其学,并知其治学方法。

启超所拟著之《清儒学案》,东原、里堂学两案,正在属稿中。

《文史通义》 章学诚著

此书虽以文史标题,实多论学术流别,宜一读。

胡适著《章实斋年谱》,可供参考。

《大同书》 康有为著

南海先生独创之思想在此书,曾刊于《不忍杂志》中。

《国故论衡》 章炳麟著

可见章太炎思想之一斑。其详当读章氏丛书。

《东西文化及其哲学》 梁漱冥著

有偏宕处,亦有独到处。

《中国哲学史大纲》上卷 胡适著

《先秦政治思想史》 梁启超著

将读先秦经部、子部书,宜鸹读此两书,可引起兴味,并启发自己之判断力。

《清代学术概论》 梁启超著

欲略知清代学风,宜读此书。

(乙)政治史及其他文献学书类

《尚书》

内中惟二十八篇是真,书宜精读,但其文佶屈赘牙,不能成诵亦无妨。余篇属晋人伪撰,一游览便足(真伪篇目,看启超所著《古书之真伪及其年代》,日内当出版)。

此书非看注释不能解,注释书以孙星衍之《尚书今古文注疏》为最好。

《逸周书》

此书真伪参半,宜一游览。

注释书有朱右曾《逸周书集训校释》颇好。

《竹书纪年》

此书现通行者为元、明人伪撰。其古本,清儒辑出者数家,王国维所辑最善。

《国语》 《春秋左氏传》

此两书或本为一书,由西汉人析出,宜合读之。《左传》宜选出若干篇熟读成诵,于学文甚有益。

读《左传》宜参观顾栋高《春秋大事表》,可以得治学方法。

《战国策》

宜选出若干篇熟读,于学文有益。

《周礼》

此书西汉末晚出,何时代人所撰,尚难断定,惟书中制度,当有一部分为周代之旧,其余亦战国秦汉间学者理想的产物,故总宜一读。

注释书有孙诒让《周礼正义》最善。

《考信录》 崔述著

此书考证三代史事实最谨严,宜一游览,以为治古史这标准。

《资治通鉴》

此为编年政治史最有价值之作品,虽卷帙稍繁,总希望学者能全部精读一过。

若苦干燥无味,不妨仿《春秋大事表》之例,自立若干门类,标治摘记作将来著述资料(吾少时曾用此法,虽无成书,然增长兴味不少)。

王船山《读通鉴论》,批评眼光,颇异俗流,读通鉴时取以并读,亦助兴之一法。

《续资治通鉴》 毕沅著

此书价值远在司马原著之下,自无待言,无视彼更优者,姑以备数耳。

或不读正《资治通鉴》而读《九种纪事本末》,亦可,要之非此则彼,必须有一书经目者。

《文献通考》 《续文献通考》 《皇朝文献通考》

三书卷帙浩繁,今为学者摘其要目:《田赋考》、《户口考》、《职役考》、《市籴考》、《征榷考》、《国用考》、《钱币考》、《兵考》、《刑考》、《经籍考》、《四裔考》,必不读;《王礼考》、《封建考》、《象纬考》,绝对不必读;其余或读或不读随人(手边无原书,不能具记其目,有漏略当校补)。

各人宜因其所嗜,择类读之。例如欲研究经济史、财政史者,则读前七才考。余仿此。

《马氏文献通考》本依仿杜氏《通典》而作。若尊创作,应《通典》。今舍彼取此者,取其资料较丰富耳。吾辈读旧史,所贵者惟在原料炉锤组织,当求之在我也。

《两汉会要》、《唐会要》、《五代会要》,可与《通考》合读。

《通志二十略》

郑渔仲史识、史才皆迈寻常。《通志》全书卷帙繁,不必读,二十略则其精神所聚,必须游览,其中与《通考》门类同者或可省。最要者,《氏族略》、《六书略》、《七音略》、《校雠略》等篇。

《二十四史》

《通鉴》、《通考》,已浩无涯涘更语及彪大之《二十四史》,学者几何不望而却走?然而《二十四史》终不可不读,其故有二:(一)现在既无满意之通史,不读《二十四史》,无以知先民活动之遗迹;(二)假令虽有佳的通史出现,然其书自有别裁。《二十四史》之原料,终不能全行收入,以故《二十四史》终久仍为国民应读之书。

书既应读,而又浩瀚难读,则如之何,吾今试为学者拟摘读之法数条。

其书皆大史学家一手著连,体例精严,且时代近古,向来学人诵习者众在学界之势力与六经诸子埒,吾辈为常识计,非一读不可。吾常希望学者将此四史之列传,全体游览一过,仍摘出若干篇稍为熟读,以资学文之助,因四史中佳文最多也(若欲吾举其目亦可,但手边无原书,当以异日)。四史之外,则《明史》共认为官修书中之最佳者,且时代最近,亦宜稍为详读。

二曰就事分类而摘读志。例如欲研究经济史、财政史,则读《平准书》、《食货志》;欲研究音乐,则读《乐书》、《乐志》;欲研究兵制,则读《兵志》;欲研究学术史,则读《艺文志》、《经籍志》,附以《儒林传》;欲研究宗教史,则读《北魏书·释老志》(可惜他史无之)。每研究一门,则通各史此门之志而读之,且与《文献通考》之此门合读。当其读时,必往往发现许多资料散见于各传者,随即跟踪调查其传以读之,如此引申触类,渐渐便能成为经济史、宗教史……等等之长编,将来荟萃而整理之,便成著述矣。

三曰就人分类而摘读传。读名人传记,最能激发人志气且,于应事接物之智慧,增长不少,古人所以贵读史者以此。全史各传既不能遍读(且亦不必),则宜择伟大人物之传读之,每史亦不过二三十篇耳。此外又可就其所欲研究者而择读,如欲研究学术史,则读《儒林传》及其他学者之专传;欲研究文学史,则读《文苑传》及其他文学家之专传。用此法读去,恐之患其少,不患其多矣。

又各史之《外国传》、《蛮夷传》、《土司传》等,包含种族史及社会学之原料最多,极有趣,吾深望学者一读之。

《廿二史札记》 赵翼著

学者读正史之前,吾劝其一游览此书。记称“属辞比事《春秋》之教”,此书深进“比事”之决,每一个题目之下,其资料皆从几十篇传中,零零碎碎觅出,如采花成蜜。学者能用其法以读史,便可养成著术能力(内中校勘文学异同之部约占三分一,不读亦可)。

《圣武记》 魏源著

《国朝先正事略》 李元度著

清朝一代史迹,至今尚无一完书可读,最为遗憾,姑举此二书充数。魏默深有良史之才,《圣武记》为纪事本末体裁,叙述绥服蒙古、勘定金川、抚循西藏……诸役,于一事之原因结果及其中间进行之次序,若指诸掌,实罕见之名著也。李次青之《先正事略》,道光以前人物略具,文亦有法度,宜一游览,以知最近二三百年史迹大概。

日本人稻叶君山所著《清朝全史》尚可读(有译本)。

《读史方舆纪要》 顾祖禹著

此为最有组织的地理书,其特长在专论形势,以地哉为经,以史迹为纬,读之不感干燥。此书卷帙虽多,专读其叙论(至各府止),亦不甚费力,且可引起地理学兴味。

《史通》 刘知几著

此书论作史方法,颇多特识,宜游览。章氏《文史通义》,性质略同,范围较广,已见前。

《中国历史研究法》 梁启超著

读之可增史不兴味,且知治史方法。

(丙)韵文书类

《诗经》

希望学者能全部熟读成诵,即不尔,亦须一大部分能其词。注释书,陈奂《诗毛氏传疏》最善。

《楚辞》

屈、宋作宜熟读,能成诵最佳,其余可不读。注释书,朱熹《楚辞集注》较可。

《文选》

择读。

《乐府诗集》 郭茂倩编

专读其中不知作者姓名之汉古辞,以见魏六朝乐府风格,其他不必读。

魏晋六朝人诗宜读以下各家:

曹子建 阮嗣宗 陶渊明 谢康乐 鲍明远 谢玄晖

无单行集者,可用张淳《汉魏百三家集本》,或王闓运《五代诗选本》。

《李太白集》 《杜工部集》 《王右丞集》 《孟襄阳集》 《韦苏州集》 《高常侍集》 《韩昌黎集》 《柳河东集》 《白香山集》 《李义山集》 《王临川集》(诗宜用李璧注本) 《苏东坡集》 《元遗山集》 《陆放翁集》

以上唐宋人诗文集

《唐百家诗选》 王安石选

《宋诗钞》 吕留良钞

以上唐宋诗选本

《清真词》(周美成) 《醉翁琴趣》(欧阳修) 《东坡乐府》(苏轼) 《屯田集》(柳永) 《淮海词》(秦观) 《樵歌》(朱敦儒) 《稼轩词》(辛弃疾) 《后村词》(刘克庄) 《石道人歌曲》(姜夔) 《碧山词》(王沂孙) 《梦窗词》(吴文英)

以上宋人词集

《西厢记》 《琵琶记》 《牡丹亭》 《桃花扇》 《长生殿》

以上元明清人曲本

本门所列书,专资学者课余讽诵,陶写情趣之用既非为文学专家说法,尢非为治文学史者说法,故不曰文学类,而曰文类。文学范围,最少应包含古文(骈散文)及小说。吾以为苟非欲作文学专家,则无专读小说这必要;至于古文,本不必别学,吾辈总须读周秦诸子、《左传》、《国策》、四史、《通鉴》及其关于思想、关于记载之著作,苟能多读,自能属文,何必格外标举一种,名曰古文耶?故专以文鸣之文集不复录(其余学问有关系之文集,散见各门)。《文选》及韩、柳、王集聊附见耳。学者如必欲就文求文,无已,则姚鼐之《古文辞类纂》、李兆洛之《骈体文钞》、曾国藩之《经史百家杂钞》可用也。

清人不以韵文见长,故除曲本数部外,其余诗词皆不复列举,无已,则于最初期与最末期各举诗词家一人:吴伟业之《梅村诗集》与黄尊宪之《人境庐诗集》、成德之《饮水词》与文焯之《樵风乐府》也。

(丁)小学书及文法类书

《说文解字注》 段玉裁著

《说文通训定声》 朱骏声著

《说文释例》 王筠著

段著为《说文》正著,朱注明音与义之关系,王著为《说文》通释,读此三书,略可通《说文》矣。

《经传释词》 王引之著

《古书疑义举例》 俞樾著

《文通》 马建忠著

读此三书,可知古人语法文法。

《经籍纂诂》 阮元著

此书汇集各字之义训,宜置备检查。

文字音韵,为清儒最擅之学,佳书林立,此仅举入门最要之数种,若非有志研究斯学者,并此诸书不读亦无妨也。

(戊)随意涉览书类

学问固贵专精,又须博览以辅之。况学者读书尚少时,不甚自知其性所近者为何。随意涉览,初时并无目的,不期而引起问题,发生兴趣,从此向某方面深造研究,遂成绝业者,往往而有也。吾固杂举有用或有趣之各书,供学者自由翻阅之娱乐。

读此者不必顺叶次,亦不必求终卷者(各书亦随忆想所及杂举,无复诠次)。

《四库全书总目提要》

清乾隆间四库馆,董其事者皆一时大学者,故所作提要,最称精审,读之可略见各书内容(中多偏至语自亦不能免)。宜先读各部类之叙录,其各书条下则随意抽阅。

有所谓存目者,其书被屏,不收入四库者也,内中颇有怪书,宜稍注意读之。

《世说新语》

将晋人谈玄语分类纂录,语多隽妙,课余暑假之良伴侣。

《水经注》 郦道元撰,戴震校

六朝人地理专书,但多描风景,记古迹,文辞华妙,学作小品文最适用。

《文心雕龙》 刘勰撰

六朝人论文书,论多精到,文亦雅丽。

《大唐三藏慈恩法师传》 慧立撰

此为玄奘法师详传。玄藏为第一位留学生,为大思想家,读之可以增第志气。

《徐霞客游记》

霞客晚明人,实一大探险家,其书极有趣。

《梦溪笔谈》 沈括

宋人笔记中含有科学思想者。

《困学纪闻》 王应麟撰,阎若璩注

宋人始为考证学者,顾亭林《日知录》颇仿其体。

《通艺录》 程瑶田撰

清代考证家之博物书。

《癸巳类稿》 俞正燮撰

多为经学以外之考证,如考棉花来历,考妇人缠足历史,辑李易安事迹等;又多新颖之论,如论妒非妇人恶德等。

《东塾读书记》 陈沣撰

此书仅五册,十余年乃成,盖合数十条笔记之长编,乃成一条笔记之定稿,用力最为精苦,读之可识搜集资料,及驾驭资料之方法。书中《论郑学》、《论朱学》、《论诸子》、《论三国》诸卷最善。

《庸盦笔记》 薛福成

多记清咸丰、同治间掌故。

《张太岳集》 张居正

江陵为明名相,其信札益人神智,文章亦美。

《王心斋先生全书》 王艮

吾常名心斋为平民的理学家,其人有生气。

《朱舜水遗集》 朱之瑜

舜水为日本文化之开辟人,惟一之国学输出者,读之可见其人格。

《李恕谷文集》 李塨

恕谷为习斋门下健将,其文劲达。

《鲒琦亭集》 全祖望

 集中记晚明掌故甚多。

《潜研堂集》 钱大昕

竹汀在清儒中最博洽者,其对伦理问题,亦颇有新论。

《述学》 汪中

容甫为治诸子学之先登者,其文格在汉晋间,极遒美。

《洪北江集》 洪亮吉

北江之学,长于地理,其小品骈体文,描写景物,美不可言。

《定盦文集》 龚自珍

吾少时心醉此集,今颇厌之。

《曾文正公全集》 曾国藩

《胡文忠公集》 胡林翼

右二集信札最可读,读之见其治事条理及朋友风义。曾涤生文章尤美。桐城派之大成。

《苕溪渔隐丛话》 胡仔

丛话中资料颇丰富。

《词苑丛谈》 徐钪(?)

惟一之词话,颇有趣。

《语石》 叶昌炽

以科学方法治金石学,极有价值。

《书林清话》 叶德辉

论列书源流及藏书掌故,甚好。

《广艺舟双辑》 康有为

论写定字,极精博,文章极美。

《剧说》 焦循

《宋元戏曲史》 王国维

二书论戏剧,极好。

即谓之涉览,自然无书不可涉,无书不可览,本不能胪举书目,若举之非累数十纸不可。右所列不伦不类之寥寥十余种,随杂忆所及当坐谭耳,若绳经义例,则笑绝冠缨矣。

附录一 最低限度之必读书目

右所列五项,倘能依法读之,则国学根柢略立,可以为将来大成之基矣。惟青年学生校课既繁,所治专门别有在,恐仍不能人人按表而读。

今再为拟一真正之最低限度如下:

《四书》、《易经》、《书经》、《诗经》、《礼记》、《左传》、《老子》、《墨子》、《庄子》、《荀子》、《韩非子》、《战国策》、《史记》、《汉书》、《后汉书》、《三国志》、《资治通鉴》(或《通鉴纪事本末》)、《宋元明史纪事本末》、《楚辞》、《文选》、《李太白集》、《杜工部集》、《韩昌黎集》、《柳河东集》、《白香山集》。其他词曲集随所好选读数种。

以上各书,无论学矿、学工程报……皆须一读,若并此未读,真不能认为中国学人矣。

]]>
https://blog.werner.wiki/the-entry-and-reading-of-the-sinology-book/feed/ 0
[转]Lisp的本质 https://blog.werner.wiki/the-essence-of-lisp/ https://blog.werner.wiki/the-essence-of-lisp/#respond Tue, 21 Mar 2017 05:36:19 +0000 http://blog.werner.wiki/?p=178 原文

简介

最初在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是一种标准化语法, 它以适合人阅读的格式来表达任意的层次化数据(hirearchical data)。象任务表(to-do list), 网页, 病历, 汽车保险单, 配置文件等等, 都是XML用武的地方。比如我们拿任务表做例子:

    <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>

解析这段数据时会发生什么情况? 解析之后的数据在内存中怎样表示? 显然, 用树来表示这种层次化数据是很恰当的。说到底, XML这种比较容易阅读的数据格式, 就是树型结构数据经过序列化之后的结果。任何可以用树来表示的数据, 同样可以用XML来表示, 反之亦然。希望你能懂得这一点, 这对下面的内容极其重要。

再进一步。还有什么类型的数据也常用树来表示? 无疑列表(list)也是一种。上过编译课吧? 还模模糊糊记得一点吧? 源代码在解析之后也是用树结构来存放的, 任何编译程序都会把源代码解析成一棵抽象语法树, 这样的表示法很恰当, 因为源代码就是层次结构的:函数包含参数和代码块, 代码快包含表达式和语句, 语句包含变量和运算符等等。

我们已经知道, 任何树结构都可以轻而易举的写成XML, 而任何代码都会解析成树, 因此,任何代码都可以转换成XML, 对不对? 我举个例子, 请看下面的函数:

    int add(int arg1, int arg2)
    {
        return arg1+arg2;
    }

能把这个函数变成对等的XML格式吗? 当然可以。我们可以用很多种方式做到, 下面是其中的一种, 十分简单:

    <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>

这个例子非常简单, 用哪种语言来做都不会有太大问题。我们可以把任何程序码转成XML,也可以把XML转回到原来的程序码。我们可以写一个转换器, 把Java代码转成XML, 另一个转换器把XML转回到Java。一样的道理, 这种手段也可以用来对付C++(这样做跟发疯差不多么。可是的确有人在做, 看看GCC-XML就知道了)。进一步说,凡是有相同语言特性而语法不同的语言, 都可以把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年冬天的那个雨夜, 一个名叫James Duncan Davidson的杰出的程序员正在研究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类装入,并执行其代码。

    <copy todir="../new/dir">
            <fileset dir="src_dir" />
    </copy>

这段文字的含义是把源目录复制到目标目录,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代码怎样实现呢? 我们可以这样做:

    CopyTask copy = new CopyTask();
    Fileset fileset = new Fileset();

    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的编程语言。考虑下面这种代码的可能性:

    <task name="Test">
        <echo message="Hello World" />
    </task>
    <Test />

如果XML支持”task”的创建, 上面这段代码就会输出”Hello World!”. 实际上, 我们可以用Java写个”task”任务, 然后用Ant-XML来扩展它。Ant可以在简单原语的基础上写出更复杂的原语, 就像其他编程语言常用的作法一样。这也就是我们一开始提到的基于XML的编程语言。这样做用处不大(你知道为甚么吗?), 但是真的很酷。

再看一回我们刚才说的Task任务。祝贺你呀, 你在看Lisp代码!!! 我说什么? 一点都不像Lisp吗? 没关系, 我们再给它收拾一下。

比XML更好

前面一节说过, Ant自我扩展没什么大用, 原因在于XML很烦琐。对于数据来说, 这个问题还不太大, 但如果代码很烦琐的话, 光是打字上的麻烦就足以抵消它的好处。你写过Ant的脚本吗? 我写过, 当脚本达到一定复杂度的时候, XML非常让人厌烦。想想看吧, 为了写结束标签, 每个词都得打两遍, 不发疯算好的!

为了解决这个问题, 我们应当简化写法。须知, XML仅仅是一种表达层次化数据的方式。我们并不是一定要使用尖括号才能得到树的序列化结果。我们完全可以采用其他的格式。其中的一种(刚好就是Lisp所采用的)格式, 叫做s表达式。s表达式要做的和XML一样, 但它的好处是写法更简单, 简单的写法更适合代码输入。后面我会详细讲s表达式。这之前我要清理一下XML的东西。考虑一下关于拷贝文件的例子:

    <copy toDir="../new/dir">
        <fileset dir="src_dir">
    </copy>

想想看在内存里面, 这段代码的解析树在内存会是什么样子? 会有一个”copy”节点, 其下有一个 “fileset”节点, 但是属性在哪里呢? 它怎样表达呢? 如果你以前用过XML, 并且弄不清楚该用元素还是该用属性, 你不用感到孤单, 别人一样糊涂着呢。没人真的搞得清楚。这个选择与其说是基于技术的理由, 还不如说是闭着眼瞎摸。从概念上来讲, 属性也是一种元素, 任何属性能做的, 元素一样做得到。XML引入属性的理由, 其实就是为了让XML写法不那么冗长。比如我们看个例子:

    <copy>
        <toDir>../new/dir</toDir>
        <fileset>
            <dir>src_dir</dir>
        </fileset>
    </copy>

两下比较, 内容的信息量完全一样, 用属性可以减少打字数量。如果XML没有属性的话,光是打字就够把人搞疯掉。

说完了属性的问题, 我们再来看一看s表达式。之所以绕这么个弯, 是因为s表达式没有属性的概念。因为s表达式非常简练, 根本没有必要引入属性。我们在把XML转换成s表达式的时候, 心里应该记住这一点。看个例子, 上面的代码译成s表达式是这样的:

   (copy
        (todir "../new/dir")
        (fileset (dir "src_dir")))

仔细看看这个例子, 差别在哪里? 尖括号改成了圆括号, 每个元素原来是有一对括号标记包围的, 现在取消了后一个(就是带斜杠的那个)括号标记。表示元素的结束只需要一个”)”就可以了。不错, 差别就是这些。这两种表达方式的转换, 非常自然, 也非常简单。s表达式打起字来, 也省事得多。第一次看s表达式(Lisp)时, 括号很烦人是吧? 现在我们明白了背后的道理, 一下子就变得容易多了。至少, 比XML要好的多。用s表达式写代码, 不单是实用, 而且也很让人愉快。s表达式具有XML的一切好处, 这些好处是我们刚刚探讨过的。现在我们看看更加Lisp风格的task例子:

    (task (name "Test")
        (echo (message "Hellow World!")))
    (Test)

用Lisp的行话来讲, s表达式称为表(list)。对于上面的例子, 如果我们写的时候不加换行, 用逗号来代替空格, 那么这个表达式看起来就非常像一个元素列表, 其中又嵌套着其他标记。

    (task, (name, "test"), (echo, (message, "Hello World!")))

XML自然也可以用这样的风格来写。当然上面这句并不是一般意义上的元素表。它实际上是一个树。这和XML的作用是一样的。称它为列表, 希望你不会感到迷惑, 因为嵌套表和树实际上是一码事。Lisp的字面意思就是表处理(list processing), 其实也可以称为树处理, 这和处理XML节点没有什么不同。

经受这一番折磨以后, 现在我们终于相当接近Lisp了, Lisp的括号的神秘本质(就像许多Lisp狂热分子认为的)逐渐显现出来。现在我们继续研究其他内容。

重新审视C语言的宏

到了这里, 对XML的讨论你大概都听累了, 我都讲累了。我们先停一停, 把树, s表达式,Ant这些东西先放一放, 我们来说说C的预处理器。一定有人问了, 我们的话题和C有什么关系? 我们已经知道了很多关于元编程的事情, 也探讨过专门写代码的代码。理解这问题有一定难度, 因为相关讨论文章所使用的编程语言, 都是你们不熟悉的。但是如果只论概念的话, 就相对要简单一些。我相信, 如果以C语言做例子来讨论元编程, 理解起来一定会容易得多。好, 我们接着看。

一个问题是, 为什么要用代码来写代码呢? 在实际的编程中, 怎样做到这一点呢? 到底元编程是什么意思? 你大概已经听说过这些问题的答案, 但是并不懂得其中缘由。为了揭示背后的真理, 我们来看一下一个简单的数据库查询问题。这种题目我们都做过。比方说,直接在程序码里到处写SQL语句来修改表(table)里的数据, 写多了就非常烦人。即便用C#3.0的LINQ, 仍然不减其痛苦。写一个完整的SQL查询(尽管语法很优美)来修改某人的地址, 或者查找某人的名字, 绝对是件令程序员倍感乏味的事情, 那么我们该怎样来解决这个问题? 答案就是: 使用数据访问层。

概念挺简单, 其要点是把数据访问的内容(至少是那些比较琐碎的部分)抽象出来, 用类来映射数据库的表, 然后用访问对象属性访问器(accessor)的办法来间接实现查询。这样就极大地简化了开发工作量。我们用访问对象的方法(或者属性赋值, 这要视你选用的语言而定)来代替写SQL查询语句。凡是用过这种方法的人, 都知道这很节省时间。当然, 如果你要亲自写这样一个抽象层, 那可是要花非常多的时间的–你要写一组类来映射表, 把属性访问转换为SQL查询, 这个活相当耗费精力。用手工来做显然是很不明智的。但是一旦你有了方案和模板, 实际上就没有多少东西需要思考的。你只需要按照同样的模板一次又一次重复编写相似代码就可以了。事实上很多人已经发现了更好的方法, 有一些工具可以帮助你连接数据库, 抓取数据库结构定义(schema), 按照预定义的或者用户定制的模板来自动编写代码。

如果你用过这种工具, 你肯定会对它的神奇效果深为折服。往往只需要鼠标点击数次, 就可以连接到数据库, 产生数据访问源码, 然后把文件加入到你的工程里面, 十几分钟的工作, 按照往常手工方式来作的话, 也许需要数百个小时人工(man-hours)才能完成。可是,如果你的数据库结构定义后来改变了怎么办? 那样的话, 你只需把这个过程重复一遍就可以了。甚至有一些工具能自动完成这项变动工作。你只要把它作为工程构造的一部分, 每次编译工程的时候, 数据库部分也会自动地重新构造。这真的太棒了。你要做的事情基本上减到了0。如果数据库结构定义发生了改变, 并在编译时自动更新了数据访问层的代码,那么程序中任何使用过时的旧代码的地方, 都会引发编译错误。

数据访问层是个很好的例子, 这样的例子还有好多。从GUI样板代码, WEB代码, COM和CORBA存根, 以及MFC和ATL等等。在这些地方, 都是有好多相似代码多次重复。既然这些代码有可能自动编写, 而程序员时间又远远比CPU时间昂贵, 当然就产生了好多工具来自动生成样板代码。这些工具的本质是什么呢? 它们实际上就是制造程序的程序。它们有一个神秘的名字, 叫做元编程。所谓元编程的本义, 就是如此。

元编程本来可以用到无数多的地方, 但实际上使用的次数却没有那么多。归根结底, 我们心里还是在盘算, 假设重复代码用拷贝粘贴的话, 大概要重复6,7次, 对于这样的工作量,值得专门建立一套生成工具吗? 当然不值得。数据访问层和COM存根往往需要重用数百次,甚至上千次, 所以用工具生成是最好的办法。而那些仅仅是重复几次十几次的代码, 是没有必要专门做工具的。不必要的时候也去开发代码生成工具, 那就显然过度估计了代码生成的好处。当然, 如果创建这类工具足够简单的话, 还是应当尽量多用, 因为这样做必然会节省时间。现在来看一下有没有合理的办法来达到这个目的。

现在, C预处理器要派上用场了。我们都用过C/C++的预处理器, 我们用它执行简单的编译指令, 来产生简单的代码变换(比方说, 设置调试代码开关), 看一个例子:

    #define triple(X) X+X+X

这一行的作用是什么? 这是一个简单的预编译指令, 它把程序中的triple(X)替换称为X+X+X。例如, 把所有的triple(5)都换成5+5+5, 然后再交给编译器编译。这就是一个简单的代码生成的例子。要是C的预处理器再强大一点, 要是能够允许连接数据库, 要是能多一些其他简单的机制, 我们就可以在我们程序的内部开发自己的数据访问层。下面这个例子, 是一个假想的对C宏的扩展:

    #get-db-schema("127.0.0.1")
    #iterate-through-tables
    #for-each-table
        class #table-name
            {
            };
    #end-for-each

我们连接数据库结构定义, 遍历数据表, 然后对每个表创建一个类, 只消几行代码就完成了这个工作。这样每次编译工程的时候, 这些类都会根据数据库的定义同步更新。显而易见, 我们不费吹灰之力就在程序内部建立了一个完整的数据访问层, 根本用不着任何外部工具。当然这种作法有一个缺点, 那就是我们得学习一套新的”编译时语言”, 另一个缺点就是根本不存在这么一个高级版的C预处理器。需要做复杂代码生成的时候, 这个语言(译者注: 这里指预处理指令, 即作者所说的”编译时语言”)本身也一定会变得相当复杂。它必须支持足够多的库和语言结构。比如说我们想要生成的代码要依赖某些ftp服务器上的文件, 预处理器就得支持ftp访问, 仅仅因为这个任务而不得不创造和学习一门新的语言,真是有点让人恶心(事实上已经存在着有此能力的语言, 这样做就更显荒谬)。我们不妨再灵活一点, 为什么不直接用 C/C++自己作为自己的预处理语言呢? 这样子的话, 我们可以发挥语言的强大能力, 要学的新东西也只不过是几个简单的指示字 , 这些指示字用来区别编译时代码和运行时代码。

    <%
        cout<<"Enter a number: ";
        cin>>n;
    %>
    for(int i=0;i< <% n %>;i++)
    {
        cout<<"hello"<<endl;
    }

你明白了吗? 在<%和%>标记之间的代码是在编译时运行的, 标记之外的其他代码都是普通代码。编译程序时, 系统会提示你输入一个数, 这个数在后面的循环中会用到。而for循环的代码会被编译。假定你在编译时输入5, for循环的代码将会是:


for(int i=0;i<5; i++) { cout<<"hello"<<endl; }

又简单又有效率, 也不需要另外的预处理语言。我们可以在编译时就充分发挥宿主语言(此处是C/C++)的强大能力, 我们可以很容易地在编译时连接数据库, 建立数据访问层, 就像JSP或者ASP创建网页那样。我们也用不着专门的窗口工具来另外建立工程。我们可以在代码中立即加入必要的工具。我们也用不着顾虑建立这种工具是不是值得, 因为这太容易了, 太简单了。这样子不知可以节省多少时间啊。

你好, Lisp

到此刻为止, 我们所知的关于Lisp的指示可以总结为一句话: Lisp是一个可执行的语法更优美的XML, 但我们还没有说Lisp是怎样做到这一点的, 现在开始补上这个话题。

Lisp有丰富的内置数据类型, 其中的整数和字符串和其他语言没什么分别。像71或者”hello”这样的值, 含义也和C++或者Java这样的语言大体相同。真正有意思的三种类型是符号(symbol), 表和函数。这一章的剩余部分, 我都会用来介绍这几种类型, 还要介绍Lisp环境是怎样编译和运行源码的。这个过程用Lisp的术语来说通常叫做求值。通读这一节内容, 对于透彻理解元编程的真正潜力, 以及代码和数据的同一性, 和面向领域语言的观念, 都极其重要。万勿等闲视之。我会尽量讲得生动有趣一些, 也希望你能获得一些启发。那好, 我们先讲符号。

大体上, 符号相当于C++或Java语言中的标志符, 它的名字可以用来访问变量值(例如currentTime, arrayCount, n, 等等), 差别在于, Lisp中的符号更加基本。在C++或Java里面, 变量名只能用字母和下划线的组合, 而Lisp的符号则非常有包容性, 比如, 加号(+)就是一个合法的符号, 其他的像-, =, hello-world, * 等等都可以是符号名。符号名的命名规则可以在网上查到。你可以给这些符号任意赋值, 我们这里先用伪码来说明这一点。假定函数set是给变量赋值(就像等号=在C++和Java里的作用), 下面是我们的例子:

    set(test, 5)            // 符号test的值为5
    set(=, 5)               // 符号=的值为5
    set(test, "hello")      // 符号test的值为字符串"hello"
    set(test, =)            // 此时符号=的值为5, 所以test的也为5
    set( * , "hello")         // 符号 * 的值为"hello"

好像有什么不对的地方? 假定我们对 * 赋给整数或者字符串值, 那做乘法时怎么办? 不管怎么说, * 总是乘法呀? 答案简单极了。Lisp中函数的角色十分特殊, 函数也是一种数据类型, 就像整数和字符串一样, 因此可以把它赋值给符号。乘法函数Lisp的内置函数, 默认赋给 * , 你可以把其他函数赋值给 * , 那样 * 就不代表乘法了。你也可以把这函数的值存到另外的变量里。我们再用伪码来说明一下:

     *(3,4)          // 3乘4, 结果是12
    set(temp,  * )    // 把 * 的值, 也就是乘法函数, 赋值给temp
    set( * , 3)       // 把3赋予 * 
     *(3,4)          // 错误的表达式,  * 不再是乘法, 而是数值3
    temp(3,4)       // temp是乘法函数, 所以此表达式的值为3乘4等于12
    set( * , temp)    // 再次把乘法函数赋予 * 
     *(3,4)          // 3乘4等于12

再古怪一点, 把减号的值赋给加号:

    set(+, -)       // 减号(-)是内置的减法函数
    +(5, 4)         // 加号(+)现在是代表减法函数, 结果是5减4等于1

这只是举例子, 我还没有详细讲函数。Lisp中的函数是一种数据类型, 和整数, 字符串,符号等等一样。一个函数并不必然有一个名字, 这和C++或者Java语言的情形很不相同。在这里函数自己代表自己。事实上它是一个指向代码块的指针, 附带有一些其他信息(例如一组参数变量)。只有在把函数赋予其他符号时, 它才具有了名字, 就像把一个数值或字符串赋予变量一样的道理。你可以用一个内置的专门用于创建函数的函数来创建函数,然后把它赋值给符号fn, 用伪码来表示就是:

    fn [a]
    {
        return  *(a, 2);
    }

这段代码返回一个具有一个参数的函数, 函数的功能是计算参数乘2的结果。这个函数还没有名字, 你可以把此函数赋值给别的符号:

    set(times-two, fn [a] {return  *(a, 2)})

我们现在可以这样调用这个函数:

    time-two(5)         // 返回10

我们先跳过符号和函数, 讲一讲表。什么是表? 你也许已经听过好多相关的说法。表, 一言以蔽之, 就是把类似XML那样的数据块, 用s表达式来表示。表用一对括号括住, 表中元素以空格分隔, 表可以嵌套。例如(这回我们用真正的Lisp语法, 注意用分号表示注释):

    ()                              ; 空表
    (1)                            ; 含一个元素的表
    (1 "test")                  ; 两元素表, 一个元素是整数1, 另一个是字符串
    (test "hello")            ; 两元素表, 一个元素是符号, 另一个是字符串
    (test (1 2) "hello")    ; 三元素表, 一个符号test, 一个含有两个元素1和2的
                                    ; 表, 最后一个元素是字符串

当Lisp系统遇到这样的表时, 它所做的, 和Ant处理XML数据所做的, 非常相似, 那就是试图执行它们。其实, Lisp源码就是特定的一种表, 好比Ant源码是一种特定的XML一样。Lisp执行表的顺序是这样的, 表的第一个元素当作函数, 其他元素当作函数的参数。如果其中某个参数也是表, 那就按照同样的原则对这个表求值, 结果再传递给最初的函数作为参数。这就是基本原则。我们看一下真正的代码:

    ( *  3 4)                 ; 相当于前面列举过的伪码 *(3,4), 即计算3乘4
    (times-two 5)           ; 返回10, times-two按照前面的定义是求参数的2倍
    (3 4)                   ; 错误, 3不是函数
    (time-two)              ; 错误, times-two要求一个参数
    (times-two 3 4)         ; 错误, times-two只要求一个参数
    (set + -)               ; 把减法函数赋予符号+
    (+ 5 4)                 ; 依据上一句的结果, 此时+表示减法, 所以返回1
    ( *  3 (+ 2 2))           ; 2+2的结果是4, 再乘3, 结果是12

上述的例子中, 所有的表都是当作代码来处理的。怎样把表当作数据来处理呢? 同样的,设想一下, Ant是把XML数据当作自己的参数。在Lisp中, 我们给表加一个前缀’来表示数据。


(set test '(1 2)) ; test的值为两元素表 (set test (1 2)) ; 错误, 1不是函数 (set test '( * 3 4)) ; test的值是三元素表, 三个元素分别是 * , 3, 4

我们可以用一个内置的函数head来返回表的第一个元素, tail函数来返回剩余元素组成的表。

    (head '( *  3 4))         ; 返回符号 * 
    (tail '( *  3 4))         ; 返回表(3 4)
    (head (tal '( *  3 4)))   ; 返回3
    (head test)             ; 返回 * 

你可以把Lisp的内置函数想像成Ant的任务。差别在于, 我们不用在另外的语言中扩展
Lisp(虽然完全可以做得到), 我们可以用Lisp自己来扩展自己, 就像上面举的 -two
函数的例子。Lisp的内置函数集十分精简, 只包含了十分必要的部分。剩下的函数都是作
为标准库来实现的。

Lisp宏

我们已经看到, 元编程在一个类似jsp的模板引擎方面的应用。我们通过简单的字符串处
理来生成代码。但是我们可以做的更好。我们先提一个问题, 怎样写一个工具, 通过查找
目录结构中的源文件来自动生成Ant脚本。

用字符串处理的方式生成Ant脚本是一种简单的方式。当然, 还有一种更加抽象, 表达能
力更强, 扩展性更好的方式, 就是利用XML库在内存中直接生成XML节点, 这样的话内存中
的节点就可以自动序列化成为字符串。不仅如此, 我们的工具还可以分析这些节点, 对已
有的XML文件做变换。通过直接处理XML节点。我们可以超越字符串处理, 使用更高层次的
概念, 因此我们的工作就会做的更快更好。

我们当然可以直接用Ant自身来处理XML变换和制作代码生成工具。或者我们也可以用Lisp
来做这项工作。正像我们以前所知的, 表是Lisp内置的数据结构, Lisp含有大量的工具来
快速有效的操作表(head和tail是最简单的两个)。而且, Lisp没有语义约束, 你可以构造
任何数据结构, 只要你原意。

Lisp通过宏(macro)来做元编程。我们写一组宏来把任务列表(to-do list)转换为专用领
域语言。

回想一下上面to-do list的例子, 其XML的数据格式是这样的:

    <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表达式是这样的:

    (todo "housework"
        (item (priority high) "Clean the house")
        (item (priority medium) "Wash the dishes")
        (item (priority medium) "Buy more soap"))

假设我们要写一个任务表的管理程序, 把任务表数据存到一组文件里, 当程序启动时, 从
文件读取这些数据并显示给用户。在别的语言里(比如说Java), 这个任务该怎么做? 我们
会解析XML文件, 从中得出任务表数据, 然后写代码遍历XML树, 再转换为Java的数据结构
(老实讲, 在Java里解析XML真不是件轻松的事情), 最后再把数据展示给用户。现在如果
用Lisp, 该怎么做?

假定要用同样思路的话, 我们大概会用Lisp库来解析XML。XML对我们来说就是一个Lisp
的表(s表达式), 我们可以遍历这个表, 然后把相关数据提交给用户。可是, 既然我们用
Lisp, 就根本没有必要再用XML格式保存数据, 直接用s表达式就好了, 这样就没有必要做
转换了。我们也用不着专门的解析库, Lisp可以直接在内存里处理s表达式。注意, Lisp
编译器和.net编译器一样, 对Lisp程序来说, 在运行时总是随时可用的。

但是还有更好的办法。我们甚至不用写表达式来存储数据, 我们可以写宏, 把数据当作代
码来处理。那该怎么做呢? 真的简单。回想一下, Lisp的函数调用格式:

     (function-name arg1 arg2 arg3)

其中每个参数都是s表达式, 求值以后, 传递给函数。如果我们用(+ 4 5)来代替arg1,
那么, 程序会先求出结果, 就是9, 然后把9传递给函数。宏的工作方式和函数类似。主要
的差别是, 宏的参数在代入时不求值。

    (macro-name (+ 4 5))

这里, (+ 4 5)作为一个表传递给宏, 然后宏就可以任意处理这个表, 当然也可以对它求
值。宏的返回值是一个表, 然后有程序作为代码来执行。宏所占的位置, 就被替换为这个
结果代码。我们可以定义一个宏把数据替换为任意代码, 比方说, 替换为显示数据给用户
的代码。

这和元编程, 以及我们要做的任务表程序有什么关系呢? 实际上, 编译器会替我们工作,
调用相应的宏。我们所要做的, 仅仅是创建一个把数据转换为适当代码的宏。

例如, 上面曾经将过的C的求三次方的宏, 用Lisp来写是这样子:

    (defmacro triple (x)
        `(+ ~x ~x ~x))

(译注: 在Common Lisp中, 此处的单引号应当是反单引号, 意思是对表不求值, 但可以对
表中某元素求值, 记号~表示对元素x求值, 这个求值记号在Common Lisp中应当是逗号。
反单引号和单引号的区别是, 单引号标识的表, 其中的元素都不求值。这里作者所用的记
号是自己发明的一种Lisp方言Blaise, 和common lisp略有不同, 事实上, 发明方言是
lisp高手独有的乐趣, 很多狂热分子都热衷这样做。比如Paul Graham就发明了ARC, 许多
记号比传统的Lisp简洁得多, 显得比较现代)

单引号的用处是禁止对表求值。每次程序中出现triple的时候,

    (triple 4)

都会被替换成:

    (+ 4 4 4)

我们可以为任务表程序写一个宏, 把任务数据转换为可执行码, 然后执行。假定我们的输
出是在控制台:

    (defmacro item (priority note)
        `(block
            (print stdout tab "Prority: " ~(head (tail priority)) endl)
            (print stdout tab "Note: " ~note endl endl)))

我们创造了一个非常小的有限的语言来管理嵌在Lisp中的任务表。这个语言只用来解决特
定领域的问题, 通常称之为DSLs(特定领域语言, 或专用领域语言)。

特定领域语言

本文谈到了两个特定领域语言, 一个是Ant, 处理软件构造。一个是没起名字的, 用于处
理任务表。两者的差别在于, Ant是用XML, XML解析器, 以及Java语言合在一起构造出来
的。而我们的迷你语言则完全内嵌在Lisp中, 只消几分钟就做出来了。

我们已经说过了DSL的好处, 这也就是Ant用XML而不直接用Java的原因。如果使用Lisp,
我们可以任意创建DSL, 只要我们需要。我们可以创建用于网站程序的DSL, 可以写多用户
游戏, 做固定收益贸易(fixed income trade), 解决蛋白质折叠问题, 处理事务问题, 等
等。我们可以把这些叠放在一起, 造出一个语言, 专门解决基于网络的贸易程序, 既有网
络语言的优势, 又有贸易语言的好处。每天我们都会收获这种方法带给我们的益处, 远远
超过Ant所能给予我们的。

用DSL解决问题, 做出的程序精简, 易于维护, 富有弹性。在Java里面, 我们可以用类来
处理问题。这两种方法的差别在于, Lisp使我们达到了一个更高层次的抽象, 我们不再受
语言解析器本身的限制, 比较一下用Java库直接写的构造脚本和用Ant写的构造脚本其间
的差别。同样的, 比较一下你以前所做的工作, 你就会明白Lisp带来的好处。

接下来

学习Lisp就像战争中争夺山头。尽管在电脑科学领域, Lisp已经算是一门古老的语言, 直
到现在仍然很少有人真的明白该怎样给初学者讲授Lisp。尽管Lisp老手们尽了很大努力,
今天新手学习Lisp仍然是困难重重。好在现在事情正在发生变化, Lisp的资源正在迅速增
加, 随着时间推移, Lisp将会越来越受关注。

Lisp使人超越平庸, 走到前沿。学会Lisp意味着你能找到更好的工作, 因为聪明的雇主会
被你与众不同的洞察力所打动。学会Lisp也可能意味着明天你可能会被解雇, 因为你总是
强调, 如果公司所有软件都用Lisp写, 公司将会如何卓越, 而这些话你的同事会听烦的。
Lisp值得努力学习吗? 那些已经学会Lisp的人都说值得, 当然, 这取决于你的判断。

你的看法呢?

这篇文章写写停停, 用了几个月才最终完成。如果你觉得有趣, 或者有什么问题, 意见或
建议, 请给我发邮件coffeemug@gmail.com, 我会很高兴收到你的反馈。

]]>
https://blog.werner.wiki/the-essence-of-lisp/feed/ 0