偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Lisp永恒之道

開(kāi)發(fā) 開(kāi)發(fā)工具
學(xué)習(xí)Lisp所收獲的是如何“自由地”表達(dá)你的思想,這正是Lisp最大的魅力所在,也是這門古老的語(yǔ)言仍然具有很強(qiáng)的生命力的根本原因。

Lisp之魅

長(zhǎng)久以來(lái),Lisp一直被許多人視為史上最非凡的編程語(yǔ)言。它不僅在50多年前誕生的時(shí)候帶來(lái)了諸多革命性的創(chuàng)新并極大地影響了后來(lái)編程語(yǔ)言的發(fā)展,即使在一大批現(xiàn)代語(yǔ)言不斷涌現(xiàn)的今天,Lisp的諸多特性仍然未被超越。當(dāng)各式各樣的編程語(yǔ)言擺在面前,我們可以從運(yùn)行效率、學(xué)習(xí)曲線、社區(qū)活躍度、廠商支持等多種不同的角度進(jìn)行評(píng)判和選擇,但我特別看中的一點(diǎn)在于語(yǔ)言能否有效地表達(dá)編程者的設(shè)計(jì)思想。學(xué)習(xí)C意味著學(xué)習(xí)如何用過(guò)程來(lái)表達(dá)設(shè)計(jì)思想,學(xué)習(xí)Java意味著學(xué)習(xí)如何用對(duì)象來(lái)表達(dá)設(shè)計(jì)思想,而雖然Lisp與函數(shù)式編程有很大的關(guān)系,但學(xué)習(xí)Lisp絕不僅僅是學(xué)習(xí)如何用函數(shù)表達(dá)設(shè)計(jì)思想。實(shí)際上,函數(shù)式編程并非Lisp的本質(zhì),在已經(jīng)掌握了lambda、高階函數(shù)、閉包、惰性求值等函數(shù)式編程概念之后,學(xué)習(xí)Lisp仍然大大加深了我對(duì)編程的理解。學(xué)習(xí)Lisp所收獲的是如何“自由地”表達(dá)你的思想,這正是Lisp***的魅力所在,也是這門古老的語(yǔ)言仍然具有很強(qiáng)的生命力的根本原因。

Lisp之源

Lisp意為表處理(List Processing),源自設(shè)計(jì)者John McCarthy于1960年發(fā)表的一篇論文《符號(hào)表達(dá)式的遞歸函數(shù)及其機(jī)器計(jì)算》。McCarthy在這篇論文中向我們展示了用一種簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)S表達(dá)式(S-expression)來(lái)表示代碼和數(shù)據(jù),并在此基礎(chǔ)上構(gòu)建一種完整的語(yǔ)言。Lisp語(yǔ)言形式簡(jiǎn)單、內(nèi)涵深刻,Paul Graham在《Lisp之根源》中將其對(duì)編程的貢獻(xiàn)與歐幾里德對(duì)幾何的貢獻(xiàn)相提并論。

Lisp之形

然而,與數(shù)學(xué)世界中簡(jiǎn)單易懂的歐氏幾何形成鮮明對(duì)比,程序世界中的Lisp卻一直是一種古老而又神秘的存在,真正理解其精妙的人還是少數(shù)。從表面上看,Lisp最明顯的特征是它“古怪”的S表達(dá)式語(yǔ)法。S表達(dá)式是一個(gè)原子(atom),或者若干S表達(dá)式組成的列表(list),表達(dá)式之間用空格分開(kāi),放入一對(duì)括號(hào)中。“列表“這個(gè)術(shù)語(yǔ)可能會(huì)容易讓人聯(lián)想到數(shù)據(jù)結(jié)構(gòu)中的鏈表之類的線形結(jié)構(gòu),實(shí)際上,Lisp的列表是一種可嵌套的樹(shù)形結(jié)構(gòu)。下面是一些S表達(dá)式的例子:

  1. foo  
  2. ()  
  3. (a b (c d) e)  
  4. (+ (* 2 3) 5)  
  5. (defun factorial (N)  
  6.     (if (= N 1)  
  7.         1  
  8.         (* N (factorial (- N 1)))  
  9.     )  

據(jù)說(shuō),這個(gè)古怪的S表達(dá)式是McCarthy在發(fā)明Lisp時(shí)候所采用的一種臨時(shí)語(yǔ)法,他實(shí)際上是準(zhǔn)備為L(zhǎng)isp加上一種被稱為M表達(dá)式(M-expression)的語(yǔ)法,然后再把M表達(dá)式編譯為S表達(dá)式。用一個(gè)通俗的類比,S表達(dá)式相當(dāng)于是JVM的字節(jié)碼,而M表達(dá)式相當(dāng)于Java語(yǔ)言,但是后來(lái)Lisp的使用者都熟悉并喜歡上了直接用S表達(dá)式編寫程序,并且他們發(fā)現(xiàn)S表達(dá)式有許多獨(dú)特的優(yōu)點(diǎn),所以M表達(dá)式的引入也就被無(wú)限期延遲了。

許多Lisp的入門文章都比較強(qiáng)調(diào)Lisp的函數(shù)式特性,而我認(rèn)為這是一種誤導(dǎo)。真正的Lisp之門不在函數(shù)式編程,而在S表達(dá)式本身,Lisp***的奧秘就藏在S表達(dá)式后面。S表達(dá)式是Lisp的語(yǔ)法基礎(chǔ),語(yǔ)法是語(yǔ)義的載體,形式是實(shí)質(zhì)的寄托。“S表達(dá)式”是程序的一種形,正如“七言”是詩(shī)的一種形,“微博”是信息的一種形。正是形的不同,讓微博與博客有了質(zhì)的差異,同樣的道理,正是S表達(dá)式讓Lisp與C、Java、SQL等語(yǔ)言有了天壤之別。

Lisp之道
一門語(yǔ)言能否有效地表達(dá)編程者的設(shè)計(jì)思想取決于其抽象機(jī)制的語(yǔ)義表達(dá)能力。根據(jù)抽象機(jī)制的不同,語(yǔ)言的抽象機(jī)制形成了面向過(guò)程、面向?qū)ο?、函?shù)式、并發(fā)式等不同的范式。當(dāng)你采用某一種語(yǔ)言,基本上就表示你已經(jīng)“面向XXX“了,你的思維方式和解決問(wèn)題的手段就會(huì)依賴于語(yǔ)言所提供的抽象方式。比如,采用Java語(yǔ)言通常意味著采用面向?qū)ο蠓治鲈O(shè)計(jì);采用Erlang通常意味著按Actor模型對(duì)并發(fā)任務(wù)進(jìn)行建模。

有經(jīng)驗(yàn)的程序員都知道,無(wú)論是面向XXX編程,程序設(shè)計(jì)都有一條“抽象原則“:What與How解耦。但是,普通語(yǔ)言的問(wèn)題就在于表達(dá)What的手段非常有限,無(wú)非是過(guò)程、類、接口、函數(shù)等幾種方式,而諸多領(lǐng)域問(wèn)題是無(wú)法直接抽象為函數(shù)或接口的。比如,你完全可以在C語(yǔ)言中定義若干函數(shù)來(lái)做到make file所做的事情,但C代碼很難像make file那樣聲明式地體現(xiàn)出target、depends等語(yǔ)義,它們只會(huì)作為實(shí)現(xiàn)細(xì)節(jié)被淹沒(méi)在一個(gè)個(gè)的C函數(shù)之中。采用OOP或是FP等其它范式也會(huì)遇到同樣的困難,也就是說(shuō)make file語(yǔ)言所代表的抽象維度與面向過(guò)程、OOP以及FP的抽象維度是正交的,使得各種范式無(wú)法直接表達(dá)出make file的語(yǔ)義。這就是普通語(yǔ)言的“剛性”特征,它要求我們必須以語(yǔ)言的抽象維度去分析和解決問(wèn)題,把問(wèn)題映射到語(yǔ)言的基本語(yǔ)法和語(yǔ)義。

更進(jìn)一步,如果仔細(xì)探究這種剛性的根源,我們會(huì)發(fā)現(xiàn)正是由于普通語(yǔ)言語(yǔ)法和語(yǔ)義的緊耦合造成了這種剛性。比如,C語(yǔ)言中printf("hello %s", name)符合函數(shù)調(diào)用語(yǔ)法,它表達(dá)了函數(shù)調(diào)用語(yǔ)義,除此之外別無(wú)他義;Java中interface IRunnable { ... }符合接口定義語(yǔ)法,它表達(dá)了接口定義語(yǔ)義,除此之外別無(wú)他義。如果你認(rèn)為“語(yǔ)法和語(yǔ)義緊耦合“是理所當(dāng)然的,看不出這有什么問(wèn)題,那么理解Lisp就會(huì)讓你對(duì)此產(chǎn)生更深的認(rèn)識(shí)。

當(dāng)你看到Lisp的(f a (b c))的時(shí)候,你會(huì)想到什么?會(huì)不會(huì)馬上聯(lián)想到函數(shù)求值或是宏擴(kuò)展?就像在C語(yǔ)言里看到gcd(10, 15)馬上想到函數(shù)調(diào)用,或者在Java里看到class A馬上想到類定義一樣。如果真是這樣,那它就是你理解Lisp的一道障礙,因?yàn)槟阋呀?jīng)習(xí)慣了順著語(yǔ)言去思考,總是在想這一句話機(jī)器怎么解釋執(zhí)行?那一句話又對(duì)應(yīng)語(yǔ)言的哪個(gè)特性?理解Lisp要反過(guò)來(lái),讓語(yǔ)言順著你,Lisp的(f a (b c))可以是任何語(yǔ)義,完全由你來(lái)定,它可以是函數(shù)定義、類定義、數(shù)據(jù)庫(kù)查詢、文件依賴關(guān)系,異步任務(wù)的執(zhí)行關(guān)系,業(yè)務(wù)規(guī)則 ...

下面我準(zhǔn)備先通過(guò)幾個(gè)具體的例子逐步展示Lisp的本質(zhì)。需要說(shuō)明的是,由于Lisp的S表達(dá)式和XML的語(yǔ)法形式都是一種樹(shù)形結(jié)構(gòu),在語(yǔ)義表達(dá)方面二者并無(wú)本質(zhì)的差別。所以,為了理解方便,下面我暫且用多數(shù)人更為熟悉的XML來(lái)寫代碼,請(qǐng)記住我們可以很輕易地把XML代碼和Lisp代碼相互轉(zhuǎn)換。

首先,我們可以輕易地用XML來(lái)定義一個(gè)求兩個(gè)數(shù)***公約數(shù)的函數(shù):

  1. <func name='gcd' return_type='int'>  
  2.     <params>  
  3.         <a type='int'/>  
  4.         <b type='int'/>  
  5.     </params>  
  6.     <body>  
  7.         <if>  
  8.            <equals>  
  9.                <a/>  
  10.                <int>0</int>  
  11.            </equals>  
  12.         </if>  
  13.         <then>  
  14.             <return><b/></return>  
  15.         </then>  
  16.         <else>  
  17.             <return>  
  18.                 <gcd>  
  19.                     <modulo><b/><a/></modulo>  
  20.                     <a/>  
  21.                 </gcd>  
  22.             </return>  
  23.         </else>  
  24.     </body>  
  25. </func> 

其次,我們可以用它來(lái)定義類:

  1. <class name="Computer">  
  2.     <field access="private" type="MainBoard" name="main-board" />  
  3.     <field access="private" type="CPU" name="cpu" />  
  4.     <field access="private" type="Memory" name="memory" />  
  5.     <method access="public" return_type="boolean" name="powerOn" />  
  6.         <params>...</params>  
  7.         <body>...</body>  
  8.     </method>  
  9.     <method access="public" return_type="boolean" name="powerOff" />  
  10.         <params>...</params>  
  11.         <body>...</body>  
  12.     </method>  
  13. </class

還可以輕易地用它來(lái)編寫關(guān)系查詢:

  1. <sql>  
  2.     <select>  
  3.         <column name="employees.id" />  
  4.         <column name="bonus.amount" />  
  5.     </select>  
  6.     <from>  
  7.         <table name="employees" />  
  8.         <table name="bonus" />  
  9.     </from>  
  10.     <where>  
  11.         <equals>  
  12.             <column name="employees.id" />  
  13.             <column name="bonus.employee_id" />  
  14.         </equals>  
  15.     </where>  
  16. </sql> 

還可以用它來(lái)實(shí)現(xiàn)類似make file的自動(dòng)化構(gòu)建(語(yǔ)法取自ant):

  1. <project name="MyProject" default="dist" basedir=".">  
  2.     <property name="src" location="src"/>  
  3.     <property name="build" location="build"/>  
  4.     <property name="dist"  location="dist"/>  
  5.     <target name="init">  
  6.         <mkdir dir="${build}"/>  
  7.     </target>  
  8.     <target name="compile" depends="init" description="compile the source " >  
  9.         <javac srcdir="${src}" destdir="${build}"/>  
  10.     </target>  
  11.     <target name="dist" depends="compile" description="generate the distribution" >  
  12.         <mkdir dir="${dist}/lib"/>  
  13.         <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>  
  14.     </target>  
  15.     <target name="clean" description="clean up" >  
  16.         <delete dir="${build}"/>  
  17.         <delete dir="${dist}"/>  
  18.     </target>  
  19. </project> 

一口氣舉了這么多個(gè)例子,目的在于用XML這種樹(shù)形結(jié)構(gòu)來(lái)說(shuō)明Lisp的S表達(dá)式所能夠描述的語(yǔ)義。不知道你是否發(fā)現(xiàn)了S表達(dá)式和XML這種樹(shù)形語(yǔ)法在語(yǔ)義構(gòu)造方面有著特別的“柔性”?我們可以輕易地用它構(gòu)造出函數(shù)、變量、條件判斷語(yǔ)義;類、屬性、方法語(yǔ)義;可以輕易地構(gòu)造出關(guān)系模型的select、where語(yǔ)義;可以輕易地構(gòu)造出make的target、depends語(yǔ)義,等等數(shù)不清的語(yǔ)義。在普通語(yǔ)言里,你可以定義一個(gè)函數(shù)、一個(gè)類,但你無(wú)法為C語(yǔ)言增加匿名函數(shù)特性,也沒(méi)法給Java語(yǔ)言加上RAII語(yǔ)義,甚至連自己創(chuàng)造一個(gè)foreach循環(huán)都不行,而自定義語(yǔ)義意味著在Lisp之上你創(chuàng)造了一門語(yǔ)言!不管是面向過(guò)程,面向?qū)ο?,函?shù)式,還是關(guān)系模型,在Lisp里統(tǒng)統(tǒng)都變成了一種DSL,而Lisp本身也就成了一種定義語(yǔ)言的語(yǔ)言,即元語(yǔ)言(Meta Language)。

Lisp的柔性與S表達(dá)式有著密切的關(guān)系。Lisp并不限制你用S表達(dá)式來(lái)表達(dá)什么語(yǔ)義,同樣的S表達(dá)式語(yǔ)法可以表達(dá)各種不同領(lǐng)域的語(yǔ)義,這就是語(yǔ)法和語(yǔ)義解耦。如果說(shuō)普通語(yǔ)言的剛性源于“語(yǔ)法和語(yǔ)義緊耦合”,那么Lisp的柔性正是源于“語(yǔ)法和語(yǔ)義解耦”!“語(yǔ)法和語(yǔ)義解耦”使得Lisp可以隨意地構(gòu)造各種領(lǐng)域的DSL,而不強(qiáng)制用某一種范式或是領(lǐng)域視角去分析和解決問(wèn)題。本質(zhì)上,Lisp編程是一種超越了普通編程范式的范式,這就是Lisp之道:面向語(yǔ)言編程(LOP, Language Oriented Programming)。Wikipedia上是這樣描述LOP的:

Language oriented programming (LOP) is a style of computer programming in which, rather than solving problems in general-purpose programming languages, the programmer creates one or more domain-specific languages for the problem first, and solves the problem in those languages ... The concept of Language Oriented Programming takes the approach to capture requirements in the user's terms, and then to try to create an implementation language as isomorphic as possible to the user's descriptions, so that the mapping between requirements and implementation is as direct as possible.

LOP范式的基本思想是從問(wèn)題出發(fā),先創(chuàng)建一門描述領(lǐng)域模型的DSL,再用DSL去解決問(wèn)題,它具有高度的聲明性和抽象性。SQL、make file、CSS等DSL都可以被認(rèn)為是LOP的具體實(shí)例,下面我們?cè)偻ㄟ^(guò)兩個(gè)常見(jiàn)的例子來(lái)理解LOP的優(yōu)勢(shì)。

例1:在股票交易系統(tǒng)中,交易協(xié)議定義若干二進(jìn)制的消息格式,交易所和客戶端需要對(duì)消息進(jìn)行編碼和解碼。

消息格式是一種抽象的規(guī)范,本身不對(duì)語(yǔ)言做任何的限制,你可以用C,C++,Java,或者Python。普通的實(shí)現(xiàn)方式是按照消息格式規(guī)范,在相應(yīng)的語(yǔ)言中定義消息結(jié)構(gòu),并編寫相應(yīng)的編解碼函數(shù)。假設(shè)為一個(gè)消息定義結(jié)構(gòu)和實(shí)現(xiàn)編解碼函數(shù)的工作量為M,不同消息類型的數(shù)量為N,這種方式的工作量大致為M*N。也就是說(shuō)每增加一種消息類型,就需要為該消息定義結(jié)構(gòu),實(shí)現(xiàn)編解碼函數(shù),引入bug的可能性當(dāng)然也和M*N成正比。如果仔細(xì)觀察不難發(fā)現(xiàn),各個(gè)消息結(jié)構(gòu)其實(shí)是高度類似的,編解碼函數(shù)也大同小異,但是普通語(yǔ)言卻找不到一種抽象機(jī)制能表達(dá)這種共性,比如,我們無(wú)法通過(guò)面向?qū)ο蟮姆椒ǘx一個(gè)基類把消息結(jié)構(gòu)的共性抽象出來(lái),然后讓具體的消息去繼承它,達(dá)到復(fù)用的目的。這正是由于普通語(yǔ)言的抽象維度限制所致,在普通語(yǔ)言中,你只能從函數(shù)、接口等維度對(duì)事物進(jìn)行抽象,而恰好消息格式共性所在的維度與這些抽象維度并不匹配。

其實(shí),不同消息類型的共性在于它們都具有相同的領(lǐng)域語(yǔ)義,比如:“某字段內(nèi)容是另一個(gè)字段內(nèi)容的md5碼”就是一種消息格式的領(lǐng)域語(yǔ)義,這種領(lǐng)域語(yǔ)義是OOP的抽象機(jī)制無(wú)法描述的。LOP的思路是先創(chuàng)建一門消息定義DSL,比如,類似Google的Protocol Buffer,Android的AIDL。然后,通過(guò)DSL編寫消息定義文件,直接聲明式地描述消息的結(jié)構(gòu)特征,比如,我們可以聲明式地描述“某字段內(nèi)容是另一個(gè)字段內(nèi)容的md5碼”。我們還需要為DSL開(kāi)發(fā)編譯器用于生成C、Java等通用語(yǔ)言的消息定義和編解碼函數(shù)。

有了消息定義DSL和編譯器之后,由于DSL編寫消息定義是一種高度聲明式的編程方法,每增加一種消息的只需要多編寫一個(gè)消息定義文件而已,工作量幾乎可以忽略不計(jì)。所有的工作量都集中在編譯器的開(kāi)發(fā)上,工作量是一個(gè)常數(shù)C,與消息的數(shù)量沒(méi)有關(guān)系;質(zhì)量保證方面也只需要關(guān)注編譯器這一點(diǎn),不會(huì)因?yàn)樵黾有碌南㈩愋投隻ug。

例2:在圖書管理系統(tǒng)中,需要支持在管理界面上對(duì)書籍、學(xué)生、班級(jí)等各種實(shí)體進(jìn)行管理操作。

如果按傳統(tǒng)的三層架構(gòu),一般需要在后端程序中為每一種實(shí)體定義一個(gè)類,并定義相應(yīng)的方法實(shí)現(xiàn)CRUD操作,與之相應(yīng)的,還需要在前端頁(yè)面中為每一個(gè)實(shí)體編寫相應(yīng)的管理頁(yè)面。這些實(shí)體類的CRUD操作都是大同小異的,但細(xì)節(jié)又各不相同,雖然我們很想復(fù)用某些共同的設(shè)計(jì)實(shí)現(xiàn),但OOP所提供的封裝、繼承、多態(tài)等抽象機(jī)制不足以有效捕獲實(shí)體之間的共性,大量的代碼還是必須放在子類中來(lái)完成。比如,Student和Book實(shí)體類的實(shí)現(xiàn)非常相似,但是如果要通過(guò)OOP的方式去抽象它們的共性,得出的結(jié)果多半是Entity這樣的大而空的基類,很難起到復(fù)用的效果。

其實(shí),不同實(shí)體之間的共性還是在于它們具有相同的領(lǐng)域語(yǔ)義,比如:實(shí)體具有屬性,屬性具有類型,屬性具有取值范圍,屬性不具有可讀取、可編輯等訪問(wèn)屬性,實(shí)體之間有關(guān)聯(lián)關(guān)系等。LOP方法正是直接面向這種領(lǐng)域語(yǔ)義的。采用LOP方法,我們并不需要為每一個(gè)實(shí)體類單獨(dú)編寫CRUD方法,也不需要單獨(dú)編寫管理頁(yè)面,只需要定義一種DSL并實(shí)現(xiàn)其編譯器;然后,用DSL聲明式地編寫實(shí)體描述文件,去描述實(shí)體的屬性列表,屬性的類型、取值范圍,屬性所支持的操作,屬性之間的關(guān)系和約束條件等;***,通過(guò)這個(gè)實(shí)體描述文件自動(dòng)生成后端的實(shí)體類和前端管理頁(yè)面。采用LOP,不論前后端采用何種技術(shù),Java也好,C#也好,JSP也好,ASP.NET也好,都可以自動(dòng)生成它們的代碼。采用LOP的工作量和質(zhì)量都集中在DSL的設(shè)計(jì)和編譯器的開(kāi)發(fā),與實(shí)體的數(shù)量無(wú)關(guān),也就是說(shuō),越是龐大的系統(tǒng),實(shí)體類越多越是能體現(xiàn)LOP的優(yōu)勢(shì)。

通過(guò)上面兩個(gè)小例子我們可以感受到,LOP是一種面向領(lǐng)域的,高度聲明式的編程方式,它的抽象維度與領(lǐng)域模型的維度完全一致。LOP能讓程序員從復(fù)雜的實(shí)現(xiàn)細(xì)節(jié)中解脫出來(lái),把關(guān)注點(diǎn)集中在問(wèn)題的本質(zhì)上,從而提高編程的效率和質(zhì)量。

接下來(lái)的問(wèn)題是如果需要為某領(lǐng)域設(shè)計(jì)DSL,我們是應(yīng)該發(fā)明一門類似SQL這樣的專用DSL呢,還是用XML或S表達(dá)式去定義DSL呢?它們各有何優(yōu)缺點(diǎn)呢?

我認(rèn)為采用XML或S表達(dá)式定義DSL的優(yōu)點(diǎn)主要有:1) SQL、make file、CSS等專用DSL都只能面向各自的領(lǐng)域,而一個(gè)實(shí)際的領(lǐng)域問(wèn)題通常是跨越多個(gè)領(lǐng)域的,有時(shí)我們需要將不同領(lǐng)域融合在一起,但是由于普通語(yǔ)言的剛性,多語(yǔ)言融合通常會(huì)是一件非常困難的事情,而XML和S表達(dá)式語(yǔ)法結(jié)構(gòu)的單一性和“代碼及數(shù)據(jù)”的特點(diǎn)使得跨領(lǐng)域融合毫無(wú)障礙。2) 在為DSL開(kāi)發(fā)編譯器或解釋器的方面,二者難度不同。對(duì)XML和S表達(dá)式定義的DSL進(jìn)行語(yǔ)法分析非常簡(jiǎn)單,相比之下,對(duì)SQL這樣的專用DSL進(jìn)行語(yǔ)法分析,雖然可以借助Lex、Yacc、ANTLR等代碼生成工具,但總的來(lái)講復(fù)雜度還是要明顯高一些。

當(dāng)然,XML和S表達(dá)式的優(yōu)點(diǎn)也正好是其缺點(diǎn),由于XML和S表達(dá)式的語(yǔ)法形式是固定的,不能像專用DSL那樣自由地設(shè)計(jì)語(yǔ)法。所以,一般來(lái)講專用DSL的語(yǔ)法顯得更加簡(jiǎn)潔。換句話說(shuō),XML和Lisp其實(shí)是在語(yǔ)法和語(yǔ)義間做了一個(gè)交換,用語(yǔ)法的限制換來(lái)了語(yǔ)義的靈活。

Lisp之器

接下來(lái)我們繼續(xù)探討DSL的解釋執(zhí)行問(wèn)題。DSL代碼的解釋執(zhí)行一般分為3種典型的方式:1) 通過(guò)專門的解釋器解釋執(zhí)行;2) 編譯生成其他語(yǔ)言的代碼,再通過(guò)其他語(yǔ)言的解釋器解釋執(zhí)行(或編譯運(yùn)行);3) 自解釋。比如,第1類的代表是SQL,上一節(jié)舉的兩個(gè)例子都屬于第2類,而第3類自解釋正是Lisp的特色。

為了理解自解釋,我們可以先從內(nèi)部DSL的解釋執(zhí)行說(shuō)起。內(nèi)部DSL是指嵌入在宿主語(yǔ)言中的DSL,比如,Google Test單元測(cè)試框架定義了一套基于流暢接口(Fluent Interface)的C++單元測(cè)試DSL。從語(yǔ)義構(gòu)造的角度看,內(nèi)部DSL直接借用宿主語(yǔ)言的語(yǔ)法定義了自己的領(lǐng)域語(yǔ)義,是一種語(yǔ)法和語(yǔ)義解耦;從解釋執(zhí)行的角度看,內(nèi)部DSL是隨宿主語(yǔ)言的解釋器而自動(dòng)解釋的,不需要像外部DSL一樣開(kāi)發(fā)專門的解釋器,因而實(shí)現(xiàn)的代價(jià)很低。當(dāng)然,并不是說(shuō)設(shè)計(jì)內(nèi)部DSL不用關(guān)心任何的解釋實(shí)現(xiàn),實(shí)際上,還是需要熟悉宿主語(yǔ)言的特性,并利用該特性使得DSL能隨著宿主語(yǔ)言的解釋器得到解釋執(zhí)行。

Lisp擁有強(qiáng)大的自解釋特性,這得益于***的Lisp之器:宏 (macro)。宏使得Lisp編寫的DSL可以被Lisp解釋器直接解釋執(zhí)行,這在原理上與內(nèi)部DSL是相通的,只是內(nèi)部DSL一般是利用宿主語(yǔ)言的鏈?zhǔn)秸{(diào)用等特性,通常形式簡(jiǎn)陋,功能有限,而Lisp的宏則要強(qiáng)大和靈活得多。

C語(yǔ)言中也有宏的概念,不過(guò)Lisp的宏與C語(yǔ)言的宏完全不同,C語(yǔ)言的宏是簡(jiǎn)單的字符串替換。比如,下面的宏定義:

#define square(x) (x*x)

square(1+1)的期望結(jié)果是4,而實(shí)際上它會(huì)被替換成(1+1*1+1),結(jié)果是3。這個(gè)例子說(shuō)明,C語(yǔ)言的宏只在預(yù)編譯階段進(jìn)行簡(jiǎn)單的字符串替換,對(duì)程序語(yǔ)法結(jié)構(gòu)缺乏理解,非常脆弱。Lisp的宏不是簡(jiǎn)單的字符串替換,而是一套完整的代碼生成系統(tǒng),它是在語(yǔ)法解析的基礎(chǔ)上把Lisp代碼從一種形式轉(zhuǎn)換為另一種形式,本質(zhì)上起到了普通語(yǔ)言編譯器的作用。不同的是,普通編譯器是把一種語(yǔ)言的代碼轉(zhuǎn)換為另一種語(yǔ)言的代碼,比如,Java編譯器把Java代碼轉(zhuǎn)換成Java字節(jié)碼;而Lisp宏的輸入和輸出都是S表達(dá)式,它本質(zhì)上是把一種DSL轉(zhuǎn)換為另一種DSL。下面的例子是宏的一個(gè)典型用法。

例3:假設(shè)Lisp解釋器已經(jīng)具備解釋執(zhí)行面向過(guò)程DSL的能力,需要實(shí)現(xiàn)類似ant的自動(dòng)化構(gòu)建工具。

我們可以基于宏構(gòu)建一門類ant的DSL,宏的作用是把類ant DSL通過(guò)宏展開(kāi)變成面向過(guò)程的DSL,***被Lisp解釋器所解釋執(zhí)行。這樣用Lisp編寫的ant DSL就不需要被編譯為其他語(yǔ)言,也不需要像XML的ant一樣依賴于專門的解釋器了。

當(dāng)然,和開(kāi)發(fā)專門的解釋器/編譯器相比,Lisp的宏也并非沒(méi)有缺點(diǎn),宏難以理解,開(kāi)發(fā)和調(diào)試更加困難。到底是開(kāi)發(fā)專門的解釋器/編譯器還是直接采用宏應(yīng)該視具體情況而定。

總結(jié)

Lisp采用單一的S表達(dá)式語(yǔ)法表達(dá)不同的語(yǔ)義,實(shí)現(xiàn)了語(yǔ)法和語(yǔ)義解耦。這使得Lisp具有強(qiáng)大的語(yǔ)義構(gòu)造能力,擅長(zhǎng)于構(gòu)造DSL實(shí)現(xiàn)面向語(yǔ)言編程,而宏使得Lisp具有自解釋能力,讓不同DSL之間的轉(zhuǎn)換游刃有余。進(jìn)入Lisp的世界應(yīng)當(dāng)從理解面向語(yǔ)言編程入門,這是Lisp之道,而函數(shù)式編程和宏皆為L(zhǎng)isp之器,以道馭器方為正途。

后記

本文是我學(xué)習(xí)Lisp的一個(gè)總結(jié),也是寫給有興趣學(xué)習(xí)Lisp的程序員的入門資料。必須說(shuō)明,我還是一個(gè)標(biāo)準(zhǔn)的Lisp初學(xué)者,幾乎沒(méi)有寫過(guò)像樣的Lisp程序,文中的錯(cuò)誤和不足在所難免,希望讀者批評(píng)指正,感謝!

原文鏈接:http://www.cnblogs.com/weidagang2046/archive/2012/06/03/tao_of_lisp.html

【編輯推薦】

  1. 天才的程序員使用Lisp語(yǔ)言
  2. 2010年12月編程語(yǔ)言排行榜:觸摸經(jīng)典語(yǔ)言化石之Lisp
  3. 是什么讓Lisp語(yǔ)言如此先進(jìn)?
  4. Lisp介紹之七個(gè)原始操作符
  5. 編程語(yǔ)言的王道:Lisp之美
責(zé)任編輯:彭凡 來(lái)源: 博客園
相關(guān)推薦

2010-03-19 09:27:04

云計(jì)算思科HP

2012-09-28 13:23:43

編程語(yǔ)言語(yǔ)言進(jìn)化程序員

2013-03-29 10:13:22

lisp編譯器

2009-09-03 18:32:43

Lisp函數(shù)

2017-03-16 09:30:56

LispAI數(shù)據(jù)結(jié)構(gòu)

2020-12-16 06:34:16

MySQL字符集服務(wù)器

2017-01-05 13:31:33

Lisp加法運(yùn)算

2012-11-22 10:11:16

LispLisp教程

2011-04-15 09:23:33

IETFLISP路由器

2014-12-24 13:53:48

2010-10-15 10:35:18

2011-10-14 09:20:48

Lisp

2013-03-18 09:30:18

Lisp

2015-07-30 14:45:19

java簡(jiǎn)潔

2012-08-01 09:38:17

代碼整潔

2011-05-19 08:19:50

Lisp

2022-08-31 12:15:09

JavaScript代碼優(yōu)化

2014-07-21 14:40:43

Android內(nèi)存

2014-07-28 15:01:56

Android內(nèi)存

2009-08-06 09:13:36

Ruby on Rai
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)