LISP之根源
約翰麥卡錫于1960年發(fā)表了一篇非凡的論文,他在這篇論文中對編程的貢獻(xiàn)有如歐幾里德對幾何的貢獻(xiàn).1 他向我們展示了,在只給定幾個(gè)簡單的操作符和一個(gè)表示函數(shù)的記號的基礎(chǔ)上, 如何構(gòu)造出一個(gè)完整的編程語言. 麥卡錫稱這種語言為Lisp, 意為List Processing, 因?yàn)樗闹饕枷胫皇怯靡环N簡單的數(shù)據(jù)結(jié)構(gòu)表(list)來代表代碼和數(shù)據(jù).
值得注意的是,麥卡錫所作的發(fā)現(xiàn),不僅是計(jì)算機(jī)史上劃時(shí)代的大事, 而且是一種在我們這個(gè)時(shí)代編程越來越趨向的模式.我認(rèn)為目前為止只有兩種真正干凈利落, 始終如一的編程模式:C語言模式和Lisp語言模式.此二者就象兩座高地, 在它們中間是尤如沼澤的低地.隨著計(jì)算機(jī)變得越來越強(qiáng)大,新開發(fā)的語言一直在堅(jiān)定地趨向于Lisp模式. 二十年來,開發(fā)新編程語言的一個(gè)流行的秘決是,取C語言的計(jì)算模式,逐漸地往上加Lisp模式的特性,例如運(yùn)行時(shí)類型和無用單元收集.
在這篇文章中我盡可能用最簡單的術(shù)語來解釋約翰麥卡錫所做的發(fā)現(xiàn). 關(guān)鍵是我們不僅要學(xué)習(xí)某個(gè)人四十年前得出的有趣理論結(jié)果, 而且展示編程語言的發(fā)展方向. Lisp的不同尋常之處--也就是它優(yōu)質(zhì)的定義--是它能夠自己來編寫自己. 為了理解約翰麥卡錫所表述的這個(gè)特點(diǎn),我們將追溯他的步伐,并將他的數(shù)學(xué)標(biāo)記轉(zhuǎn)換成能夠運(yùn)行的Common Lisp代碼.
七個(gè)原始操作符
開始我們先定義表達(dá)式.表達(dá)式或是一個(gè)原子(atom),它是一個(gè)字母序列(如 foo),或是一個(gè)由零個(gè)或多個(gè)表達(dá)式組成的表(list), 表達(dá)式之間用空格分開, 放入一對括號中. 以下是一些表達(dá)式:
foo()
(foo)
(foo?bar)
(a?b?(c)?d) 最后一個(gè)表達(dá)式是由四個(gè)元素組成的表, 第三個(gè)元素本身是由一個(gè)元素組成的表.
在算術(shù)中表達(dá)式 1 + 1 得出值2. 正確的Lisp表達(dá)式也有值. 如果表達(dá)式e得出值v,我們說e返回v. 下一步我們將定義幾種表達(dá)式以及它們的返回值.
如果一個(gè)表達(dá)式是表,我們稱第一個(gè)元素為操作符,其余的元素為自變量.我們將定義七個(gè)原始(從公理的意義上說)操作符: quote,atom,eq,car,cdr,cons,和 cond.
a
>?'a
a
>?(quote?(a?b?c))
(a?b?c)
t
>?(atom?'(a?b?c))
()
>?(atom?'())
t
既然有了一個(gè)自變量需要求值的操作符, 我們可以看一下quote的作用. 通過引用(quote)一個(gè)表,我們避免它被求值. 一個(gè)未被引用的表作為自變量傳給象 atom這樣的操作符將被視為代碼:
>?(atom?(atom?'a))t
反之一個(gè)被引用的表僅被視為表, 在此例中就是有兩個(gè)元素的表:
>?(atom?'(atom?'a))()
這與我們在英語中使用引號的方式一致. Cambridge(劍橋)是一個(gè)位于麻薩諸塞州有90000人口的城鎮(zhèn). 而``Cambridge''是一個(gè)由9個(gè)字母組成的單詞.
引用看上去可能有點(diǎn)奇怪因?yàn)闃O少有其它語言有類似的概念. 它和Lisp最與眾不同的特征緊密聯(lián)系:代碼和數(shù)據(jù)由相同的數(shù)據(jù)結(jié)構(gòu)構(gòu)成, 而我們用quote操作符來區(qū)分它們.
t
>?(eq?'a?'b)
()
>?(eq?'()?'())
t
a
(b?c)
(a?b?c)
>?(cons?'a?(cons?'b?(cons?'c?'())))
(a?b?c)
>?(car?(cons?'a?'(b?c)))
a
>?(cdr?(cons?'a?'(b?c)))
(b?c)
????????((atom?'a)??'second))
second
當(dāng)表達(dá)式以七個(gè)原始操作符中的五個(gè)開頭時(shí),它的自變量總是要求值的.2 我們稱這樣 的操作符為函數(shù).
函數(shù)的表示
接著我們定義一個(gè)記號來描述函數(shù).函數(shù)表示為(lambda (p1) e),其中 p1是原子(叫做參數(shù)),e是表達(dá)式. 如果表達(dá)式的第一個(gè)元素形式如上((lambda (p1) e) a1...an)
則稱為函數(shù)調(diào)用.它的值計(jì)算如下.每一個(gè)表達(dá)式ai先求值,然后e再求值.在e的求值過程中,每個(gè)出現(xiàn)在e中的pi的值是相應(yīng)的ai在最近一次的函數(shù)調(diào)用中的值.
>?((lambda?(x)?(cons?x?'(b)))?'a)(a?b)
>?((lambda?(x?y)?(cons?x?(cdr?y)))
???'z
???'(a?b?c))
(z?b?c) 如果一個(gè)表達(dá)式的第一個(gè)元素f是原子且f不是原始操作符
(f a1...an)
并且f的值是一個(gè)函數(shù)(lambda (p1)),則以上表達(dá)式的值就是
((lambda (p1) e) a1...an)
的值. 換句話說,參數(shù)在表達(dá)式中不但可以作為自變量也可以作為操作符使用:
>?((lambda?(f)?(f?'(b?c)))???'(lambda?(x)?(cons?'a?x)))
(a?b?c)
有另外一個(gè)函數(shù)記號使得函數(shù)能提及它本身,這樣我們就能方便地定義遞歸函數(shù).3 記號
(label f (lambda (p1) e))
表示一個(gè)象(lambda (p1) e)那樣的函數(shù),加上這樣的特性: 任何出現(xiàn)在e中的f將求值為此label表達(dá)式, 就好象f是此函數(shù)的參數(shù).
假設(shè)我們要定義函數(shù)(subst x y z), 它取表達(dá)式x,原子y和表z做參數(shù),返回一個(gè)象z那樣的表, 不過z中出現(xiàn)的y(在任何嵌套層次上)被x代替.
>?(subst?'m?'b?'(a?b?(a?b?c)?d))(a?m?(a?m?c)?d) 我們可以這樣表示此函數(shù) (label?subst?(lambda?(x?y?z)
???????????????(cond?((atom?z)
??????????????????????(cond?((eq?z?y)?x)
????????????????????????????('t?z)))
?????????????????????('t?(cons?(subst?x?y?(car?z))
???????????????????????????????(subst?x?y?(cdr?z))))))) 我們簡記f=(label f (lambda (p1) e))為
(defun f (p1) e)
于是
(defun?subst?(x?y?z)??(cond?((atom?z)
?????????(cond?((eq?z?y)?x)
???????????????('t?z)))
????????('t?(cons?(subst?x?y?(car?z))
??????????????????(subst?x?y?(cdr?z)))))) 偶然地我們在這兒看到如何寫cond表達(dá)式的缺省子句. 第一個(gè)元素是't的子句總是會成功的. 于是
(cond (x y) ('t z))
等同于我們在某些語言中寫的
if x then y else z
一些函數(shù)
既然我們有了表示函數(shù)的方法,我們根據(jù)七個(gè)原始操作符來定義一些新的函數(shù). 為了方便我們引進(jìn)一些常見模式的簡記法. 我們用cxr,其中x是a或d的序列,來簡記相應(yīng)的car和cdr的組合. 比如(cadr e)是(car (cdr e))的簡記,它返回e的第二個(gè)元素. >?(cadr?'((a?b)?(c?d)?e))(c?d)
>?(caddr?'((a?b)?(c?d)?e))
e
>?(cdar?'((a?b)?(c?d)?e))
(b) 我們還用(list e1...en)表示(cons e1...(cons en'()) ...). >?(cons?'a?(cons?'b?(cons?'c?'())))
(a?b?c)
>?(list?'a?'b?'c)
(a?b?c)
現(xiàn)在我們定義一些新函數(shù). 我在函數(shù)名后面加了點(diǎn),以區(qū)別函數(shù)和定義它們的原始函數(shù),也避免與現(xiàn)存的common Lisp的函數(shù)沖突.
??(eq?x?'()))
>?(null.?'a)
()
>?(null.?'())
t
??(cond?(x?(cond?(y?'t)?('t?'())))
????????('t?'())))
>?(and.?(atom?'a)?(eq?'a?'a))
t
>?(and.?(atom?'a)?(eq?'a?'b))
()
??(cond?(x?'())
????????('t?'t)))
>?(not.?(eq?'a?'a))
()
>?(not.?(eq?'a?'b))
t
???(cond?((null.?x)?y)
?????????('t?(cons?(car?x)?(append.?(cdr?x)?y)))))
>?(append.?'(a?b)?'(c?d))
(a?b?c?d)
>?(append.?'()?'(c?d))
(c?d)
??(cond?((and.?(null.?x)?(null.?y))?'())
????????((and.?(not.?(atom?x))?(not.?(atom?y)))
?????????(cons?(list?(car?x)?(car?y))
???????????????(pair.?(cdr)?(cdr?y))))))
>?(pair.?'(x?y?z)?'(a?b?c))
((x?a)?(y?b)?(z?c))
??(cond?((eq?(caar?y)?x)?(cadar?y))
????????('t?(assoc.?x?(cdr?y)))))
>?(assoc.?'x?'((x?a)?(y?b)))
a
>?(assoc.?'x?'((x?new)?(x?a)?(y?b)))
new
一個(gè)驚喜
因此我們能夠定義函數(shù)來連接表,替換表達(dá)式等等.也許算是一個(gè)優(yōu)美的表示法, 那下一步呢? 現(xiàn)在驚喜來了. 我們可以寫一個(gè)函數(shù)作為我們語言的解釋器:此函數(shù)取任意Lisp表達(dá)式作自變量并返回它的值. 如下所示: (defun?eval.?(e?a)??(cond?
????((atom?e)?(assoc.?e?a))
????((atom?(car?e))
?????(cond?
???????((eq?(car?e)?'quote)?(cadr?e))
???????((eq?(car?e)?'atom)??(atom???(eval.?(cadr?e)?a)))
???????((eq?(car?e)?'eq)????(eq?????(eval.?(cadr?e)?a)
????????????????????????????????????(eval.?(caddr?e)?a)))
???????((eq?(car?e)?'car)???(car????(eval.?(cadr?e)?a)))
???????((eq?(car?e)?'cdr)???(cdr????(eval.?(cadr?e)?a)))
???????((eq?(car?e)?'cons)??(cons???(eval.?(cadr?e)?a)
????????????????????????????????????(eval.?(caddr?e)?a)))
???????((eq?(car?e)?'cond)??(evcon.?(cdr?e)?a))
???????('t?(eval.?(cons?(assoc.?(car?e)?a)
????????????????????????(cdr?e))
??????????????????a))))
????((eq?(caar?e)?'label)
?????(eval.?(cons?(caddar?e)?(cdr?e))
????????????(cons?(list?(cadar?e)?(car?e))?a)))
????((eq?(caar?e)?'lambda)
?????(eval.?(caddar?e)
????????????(append.?(pair.?(cadar?e)?(evlis.?(cdr??e)?a))
?????????????????????a)))))
(defun?evcon.?(c?a)
??(cond?((eval.?(caar?c)?a)
?????????(eval.?(cadar?c)?a))
????????('t?(evcon.?(cdr?c)?a))))
(defun?evlis.?(m?a)
??(cond?((null.?m)?'())
????????('t?(cons?(eval.??(car?m)?a)
??????????????????(evlis.?(cdr?m)?a))))) eval.的定義比我們以前看到的都要長. 讓我們考慮它的每一部分是如何工作的.
eval.有兩個(gè)自變量: e是要求值的表達(dá)式, a是由一些賦給原子的值構(gòu)成的表,這些值有點(diǎn)象函數(shù)調(diào)用中的參數(shù). 這個(gè)形如pair.的返回值的表叫做環(huán)境. 正是為了構(gòu)造和搜索這種表我們才寫了pair.和assoc..
eval.的骨架是一個(gè)有四個(gè)子句的cond表達(dá)式. 如何對表達(dá)式求值取決于它的類型. 第一個(gè)子句處理原子. 如果e是原子, 我們在環(huán)境中尋找它的值:
>?(eval.?'x?'((x?a)?(y?b)))a
第二個(gè)子句是另一個(gè)cond, 它處理形如(a ...)的表達(dá)式, 其中a是原子. 這包括所有的原始操作符, 每個(gè)對應(yīng)一條子句.
>?(eval.?'(eq?'a?'a)?'())t
>?(eval.?'(cons?x?'(b?c))
?????????'((x?a)?(y?b)))
(a?b?c) 這幾個(gè)子句(除了quote)都調(diào)用eval.來尋找自變量的值.
最后兩個(gè)子句更復(fù)雜些. 為了求cond表達(dá)式的值我們調(diào)用了一個(gè)叫 evcon.的輔助函數(shù). 它遞歸地對cond子句進(jìn)行求值,尋找第一個(gè)元素返回t的子句. 如果找到了這樣的子句, 它返回此子句的第二個(gè)元素.
>?(eval.?'(cond?((atom?x)?'atom)????????????????('t?'list))
?????????'((x?'(a?b))))
list
第二個(gè)子句的最后部分處理函數(shù)調(diào)用. 它把原子替換為它的值(應(yīng)該是lambda 或label表達(dá)式)然后對所得結(jié)果表達(dá)式求值. 于是
(eval.?'(f?'(b?c))???????'((f?(lambda?(x)?(cons?'a?x))))) 變?yōu)? (eval.?'((lambda?(x)?(cons?'a?x))?'(b?c))
???????'((f?(lambda?(x)?(cons?'a?x))))) 它返回(a b c).
eval.的最后cond兩個(gè)子句處理第一個(gè)元素是lambda或label的函數(shù)調(diào)用.為了對label 表達(dá)式求值, 先把函數(shù)名和函數(shù)本身壓入環(huán)境, 然后調(diào)用eval.對一個(gè)內(nèi)部有 lambda的表達(dá)式求值. 即:
(eval.?'((label?firstatom?(lambda?(x)????????????????????????????(cond?((atom?x)?x)
??????????????????????????????????('t?(firstatom?(car?x))))))
?????????y)
???????'((y?((a?b)?(c?d))))) 變?yōu)? (eval.?'((lambda?(x)
???????????(cond?((atom?x)?x)
?????????????????('t?(firstatom?(car?x)))))
?????????y)
????????'((firstatom
???????????(label?firstatom?(lambda?(x)
????????????????????????????(cond?((atom?x)?x)
??????????????????????????????????('t?(firstatom?(car?x)))))))
??????????(y?((a?b)?(c?d))))) 最終返回a.
最后,對形如((lambda (p1) e) a1...an)的表達(dá)式求值,先調(diào)用evlis.來求得自變量(a1...an)對應(yīng)的值(v1...vn),把(p1v1)...(pnvn)添加到環(huán)境里, 然后對e求值. 于是
(eval.?'((lambda?(x?y)?(cons?x?(cdr?y)))?????????'a
?????????'(b?c?d))
???????'()) 變?yōu)? (eval.?'(cons?x?(cdr?y))
???????'((x?a)?(y?(b?c?d)))) 最終返回(a c d).
后果
既然理解了eval是如何工作的, 讓我們回過頭考慮一下這意味著什么. 我們在這兒得到了一個(gè)非常優(yōu)美的計(jì)算模型. 僅用quote,atom,eq,car,cdr,cons,和cond, 我們定義了函數(shù)eval.,它事實(shí)上實(shí)現(xiàn)了我們的語言,用它可以定義任何我們想要的額外的函數(shù).
當(dāng)然早已有了各種計(jì)算模型--最著名的是圖靈機(jī). 但是圖靈機(jī)程序難以讀懂. 如果你要一種描述算法的語言, 你可能需要更抽象的, 而這就是約翰麥卡錫定義 Lisp的目標(biāo)之一.
約翰麥卡錫于1960年定義的語言還缺不少東西. 它沒有副作用, 沒有連續(xù)執(zhí)行 (它得和副作用在一起才有用), 沒有實(shí)際可用的數(shù),4 沒有動(dòng)態(tài)可視域. 但這些限制可以令人驚訝地用極少的額外代碼來補(bǔ)救. Steele和Sussman在一篇叫做``解釋器的藝術(shù)''的著名論文中描述了如何做到這點(diǎn).5
如果你理解了約翰麥卡錫的eval, 那你就不僅僅是理解了程序語言歷史中的一個(gè)階段. 這些思想至今仍是Lisp的語義核心. 所以從某種意義上, 學(xué)習(xí)約翰麥卡錫的原著向我們展示了Lisp究竟是什么. 與其說Lisp是麥卡錫的設(shè)計(jì),不如說是他的發(fā)現(xiàn). 它不是生來就是一門用于人工智能, 快速原型開發(fā)或同等層次任務(wù)的語言. 它是你試圖公理化計(jì)算的結(jié)果(之一).
隨著時(shí)間的推移, 中級語言, 即被中間層程序員使用的語言, 正一致地向Lisp靠近. 因此通過理解eval你正在明白將來的主流計(jì)算模式會是什么樣.
注釋
把約翰麥卡錫的記號翻譯為代碼的過程中我盡可能地少做改動(dòng). 我有過讓代碼更容易閱讀的念頭, 但是我還是想保持原汁原味.在約翰麥卡錫的論文中,假用f來表示, 而不是空表. 我用空表表示假以使例子能在Common Lisp中運(yùn)行. (fixme)
我略過了構(gòu)造dotted pairs, 因?yàn)槟悴恍枰鼇砝斫鈋val. 我也沒有提apply, 雖然是apply(它的早期形式, 主要作用是引用自變量), 被約翰麥卡錫在1960年稱為普遍函數(shù), eval只是不過是被apply調(diào)用的子程序來完成所有的工作.
我定義了list和cxr等作為簡記法因?yàn)辂溈ㄥa就是這么做的. 實(shí)際上 cxr等可以被定義為普通的函數(shù). List也可以這樣, 如果我們修改eval, 這很容易做到, 讓函數(shù)可以接受任意數(shù)目的自變量.
麥卡錫的論文中只有五個(gè)原始操作符. 他使用了cond和quote,但可能把它們作為他的元語言的一部分. 同樣他也沒有定義邏輯操作符and和not, 這不是個(gè)問題, 因?yàn)樗鼈兛梢员欢x成合適的函數(shù).
在eval.的定義中我們調(diào)用了其它函數(shù)如pair.和assoc.,但任何我們用原始操作符定義的函數(shù)調(diào)用都可以用eval.來代替. 即
(assoc. (car e) a) 能寫成 (eval.?'((label?assoc.????????????????(lambda?(x?y)
??????????????????(cond?((eq?(caar?y)?x)?(cadar?y))
????????????????????????('t?(assoc.?x?(cdr?y))))))
?????????(car?e)
?????????a)
????????(cons?(list?'e?e)?(cons?(list?'a?a)?a)))
麥卡錫的eval有一個(gè)錯(cuò)誤. 第16行是(相當(dāng)于)(evlis. (cdr e) a)而不是(cdr e), 這使得自變量在一個(gè)有名函數(shù)的調(diào)用中被求值兩次. 這顯示當(dāng)論文發(fā)表的時(shí)候, eval的這種描述還沒有用IBM 704機(jī)器語言實(shí)現(xiàn). 它還證明了如果不去運(yùn)行程序, 要保證不管多短的程序的正確性是多么困難.
我還在麥卡錫的論文中碰到一個(gè)問題. 在定義了eval之后, 他繼續(xù)給出了一些更高級的函數(shù)--接受其它函數(shù)作為自變量的函數(shù). 他定義了maplist:
(label?maplist???????(lambda?(x?f)
?????????(cond?((null?x)?'())
???????????????('t?(cons?(f?x)?(maplist?(cdr?x)?f)))))) 然后用它寫了一個(gè)做微分的簡單函數(shù)diff. 但是diff傳給maplist一個(gè)用x做參數(shù)的函數(shù), 對它的引用被maplist中的參數(shù)x所捕獲.6
這是關(guān)于動(dòng)態(tài)可視域危險(xiǎn)性的雄辯證據(jù), 即使是最早的更高級函數(shù)的例子也因?yàn)樗鲥e(cuò). 可能麥卡錫在1960年還沒有充分意識到動(dòng)態(tài)可視域的含意. 動(dòng)態(tài)可視域令人驚異地在Lisp實(shí)現(xiàn)中存在了相當(dāng)長的時(shí)間--直到Sussman和Steele于 1975年開發(fā)了Scheme. 詞法可視域沒使eval的定義復(fù)雜多少, 卻使編譯器更難寫了.
About this document ...
Lisp之根源This document was generated using the LaTeX2HTML translator Version 2K.1beta (1.48)
Copyright ? 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
Copyright ? 1997, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -split=0 roots_of_lisp.tex
The translation was initiated by Dai Yuwen on 2003-10-24
?
轉(zhuǎn)載于:https://www.cnblogs.com/Henrya2/archive/2009/02/07/1386010.html
總結(jié)
- 上一篇: .NET设计模式(15):结构型模式专题
- 下一篇: [转]如何讓IE7中關閉瀏覽器不出現詢問