回顧
上文我們介紹了Emacs的用法豹悬,發(fā)現(xiàn)一分鐘學(xué)會使用它并不是難事,
而且液荸,我們沒有讓快捷鍵束縛住瞻佛,因為Emacs的精髓在于Emacs Lisp中。
本文我們開始探討Emacs Lisp娇钱,不過在這之前我們還要先熟悉一下Lisp的特點和Lisp家族的成員伤柄,
隨后本文重點分析和介紹了列表,引用和求值策略文搂,
這幾個概念适刀,尤其是引用,對學(xué)習(xí)者來說非常容易引起困惑煤蹭,
本文采用了不同的角度來描述這些概念笔喉。
1. 強(qiáng)大的Lisp
1960年取视,John McCarthy發(fā)表了一篇計算機(jī)領(lǐng)域的文章,這是一篇“驚世之作”常挚,
它的作用簡直就像歐幾里得《幾何原本》對幾何學(xué)的貢獻(xiàn)一樣作谭。
John McCarthy只用了一些簡單的的運算符和函數(shù),構(gòu)建出了一門圖靈完備的編程語言奄毡,
稱之為Lisp折欠,Lisp是列表處理(List Processing)的簡稱。
這門語言的關(guān)鍵思想是吼过,不論代碼還是數(shù)據(jù)锐秦,都用統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)(列表)進(jìn)行表示。
Lisp語言具有很強(qiáng)的表達(dá)能力盗忱,我們可以用更少的代碼做更多的事情农猬。
通常而言,語言具有表達(dá)能力就必須提供豐富的內(nèi)置功能和強(qiáng)大的擴(kuò)展性售淡。
語言的內(nèi)置功能指的是語言默認(rèn)提供的功能斤葱,它能減少程序員的重復(fù)勞動,幫助他們快速完成工作揖闸。
語言的擴(kuò)展性揍堕,指的是當(dāng)語言內(nèi)置功能不能滿足需要的時候,程序員可以怎樣做汤纸。
同時具有豐富的功能和強(qiáng)大的擴(kuò)展性是很困難的衩茸,這需要在語言的設(shè)計階段就考慮好,
語言的內(nèi)置功能越多贮泞,就會越復(fù)雜楞慈,擴(kuò)展功能的與內(nèi)置功能的一致性就很難被保證。
現(xiàn)代的高級編程語言啃擦,離不開編譯器和解釋器囊蓝,
編譯器將高級語言的代碼轉(zhuǎn)換成更底層的語言,例如C語言或者匯編令蛉,
解釋器提供了一個運行時環(huán)境聚霜,直接解釋執(zhí)行高級語言的源代碼。
一般而言珠叔,編譯器是由語言的開發(fā)商提供的蝎宇,使用者并不會參與到編譯器的開發(fā)工作之中,
如果想要在語言中支持一等函數(shù)(first-class function)祷安,就必須讓語言的開發(fā)商改寫編譯器姥芥,
如果需要增加新的類似if-else的控制結(jié)構(gòu),或者讓語言支持面向?qū)ο缶幊袒惚蓿惨膶懢幾g器才行凉唐。
因此报嵌,語言支持什么功能,以及源代碼被如何編譯熊榛,完全取決于開發(fā)商。
而Lisp語言則不同腕巡,它允許程序員對編譯器進(jìn)行編程玄坦,(元編程
Lisp程序員可以決定代碼被如何編譯甚至如何被讀取,像是半個編譯器的開發(fā)者一樣绘沉。
2. Lisp-1和Lisp-2
Lisp語言構(gòu)成了一個家族煎楣,具有成百上千種方言,
用的最多的幾種是车伞,Common Lisp择懂,Scheme,Racket另玖,和Emacs Lisp困曙。
其中Scheme的目標(biāo)是簡潔,Common Lisp提供了強(qiáng)大的工業(yè)級支持谦去,
Racket提供了一種創(chuàng)造語言和設(shè)計實踐的平臺慷丽,Emacs Lisp主要用于Emacs中。
Emacs Lisp更像Common Lisp鳄哭,它們都是Lisp-2要糊,
即同一個符號在不同的上下文中,可以分別用來表示變量和函數(shù)妆丘,
而Scheme和Racket則只能用來表示同一個實體锄俄,稱為Lisp-1。
出現(xiàn)Lisp-2勺拣,主要是因為有效率方面的考慮奶赠,
在Lisp-2中,函數(shù)和變量分屬不同的名字空間药有,在不同的環(huán)境中车柠,由不同的求值器進(jìn)行處理。
這樣做也使語義更加復(fù)雜了塑猖,以后的文章中竹祷,我們會介紹Emacs Lisp中符號(Symbol)的概念。
3. 列表對象和它的文本表示
列表是Lisp語言中一種常用的數(shù)據(jù)結(jié)構(gòu)羊苟,用來表示一批數(shù)據(jù)塑陵。
例如,由整數(shù)1
蜡励,2
和3
構(gòu)成的列表對象令花,Lisp會將它打印為阻桅,(1 2 3)
。
各個列表元素用空格分隔兼都,用圓括號括起來嫂沉。
然而,在Lisp代碼中直接寫(1 2 3)
扮碧,并不會創(chuàng)建一個列表對象趟章,
因為Lisp程序也是用括號方式表示的,例如慎王,(+ 1 2)
表示對整數(shù)1
和2
進(jìn)行加法運算蚓土。
那么如何才能創(chuàng)建一個列表對象呢?
我們需要調(diào)用list
函數(shù)赖淤,(list 1 2 3)
蜀漆,這段代碼將會創(chuàng)建一個由整數(shù)1
,2
和3
構(gòu)成的列表對象咱旱,
這個列表對象打印為(1 2 3)
确丢。
注意,以上我們嚴(yán)謹(jǐn)?shù)膮^(qū)分了Lisp對象和它的打印結(jié)果吐限,
是因為對象和它的文本表示(textual representation)是不同的概念蠕嫁。
例如,在C語言中我們寫毯盈,
int result = 1 + 2;
我們實際上是用“1”表示了整數(shù)1
剃毒,“1”只是一段文本,是印刷符號搂赋,而整數(shù)1
是一個數(shù)學(xué)對象赘阀,
同樣的,“+”是一段文本脑奠,它表示了加法運算符基公。
在Lisp語言中,我們用文本來寫程序宋欺,而Lisp讀取器得到的是Lisp對象轰豆,
經(jīng)過對這些Lisp對象進(jìn)行計算,得到了計算結(jié)果齿诞,也是一個Lisp對象酸休,
最終,反饋給我們的是這個對象的文本表示祷杈。
4. 字面量和引用
在Lisp中斑司,我們用文本“1”可以直接表示整數(shù)1
,用“#t”表示真值但汞,
類似的“1”和“#t”宿刮,稱之為對象的字面量表示(literal representation)互站。
其它語言中,也提供了廣泛的字面量表示法僵缺,例如胡桃,JavaScript提供了數(shù)組和對象字面量,
const obj = {
x: 1,
y: [2, 3, 4]
};
這段代碼創(chuàng)建了一個JavaScript Object
磕潮,它的x
屬性值是1
翠胰,y
屬性值是一個數(shù)組。
字面量表示法揉抵,使得我們不必調(diào)用new Object
和new Array
來創(chuàng)建它。
Lisp中列表對象用的非常多嗤疯,每次都使用list
函數(shù)來創(chuàng)建是一件麻煩的事情冤今,
因此,Lisp語言提供了列表對象的字面量寫法茂缚,我們只需要調(diào)用quote
就可以了戏罢。
(quote (1 2 3))
以上Lisp代碼會創(chuàng)建一個打印形式為(1 2 3)
的列表對象。
對于嵌套列表脚囊,使用quote
是非常方便的龟糕,
(quote (1 (2 3) ((4 5) (6 7))))
像這樣創(chuàng)建列表的方式,稱為引用(quoting)悔耘,
這不同于按引用調(diào)用(call by reference)中的“引用”(reference)讲岁。
quote
還有一個便捷的寫法,就是用單引號來表示它衬以,(quote (1 2 3))
可以表示為缓艳,
'(1 2 3)
我們只需要在列表前加一個單引號即可,因為列表的右括號表明了它在引用這個列表看峻。
5. quote和list
值得一提的是阶淘,引用并不保證每次都會重新創(chuàng)建列表。
例如互妓,在Lisp中我們使用define
創(chuàng)建函數(shù)溪窒,
(define (foo)
'(1 2 3))
然后,用以下方式進(jìn)行函數(shù)調(diào)用冯勉,注意foo
參數(shù)個數(shù)為0個澈蚌,
(foo)
多次調(diào)用foo
,編譯器可能返回同一個列表對象灼狰。
而list
則不同惜浅,每次調(diào)用它會返回一個全新的列表對象,
(define (foo)
(list 1 2 3))
6. 求值策略
Lisp代碼是由表達(dá)式構(gòu)成的伏嗜,Lisp程序的執(zhí)行過程就是表達(dá)式的求值過程坛悉,
(* (+ 1 2) (+ 3 4))
以上表達(dá)式的求值結(jié)果為21
伐厌。
在程序的列表表示法中,從左到右位于第一個位置的元素裸影,是比較特殊的挣轨,
它表示一個函數(shù)(function),一個宏(macro)轩猩,或者一個內(nèi)置的特殊命令(special form)卷扮。
位于其他位置的元素稱為參數(shù)。
函數(shù)被調(diào)用的時候均践,它的每個參數(shù)都必須首先被求值晤锹,
例如,以上程序中*
彤委,+
都是函數(shù)鞭铆,
在調(diào)用乘法函數(shù)*
時,它的參數(shù)(+ 1 2)
和(+ 3 4)
都首先要被求值焦影,分別求值為3
和7
车遂,
然后再進(jìn)行乘法運算,結(jié)果為21
斯辰。
而宏和內(nèi)置的特殊命令舶担,并不要求如此,它們有自己的對參數(shù)的求值策略彬呻。
其中“1”稱之為自求值對象衣陶,對它進(jìn)行求值將得到它本身,
1
(eval 1)
(eval (eval 1))
其結(jié)果都為1
闸氮,其它的自求值對象還包括布爾值祖搓,字符串,向量湖苞,等等拯欧。
(+ 1 2)
中1
和2
之前沒有quote
,是因為它們是自求值對象财骨,(+ '1 '2)
和(+ 1 2)
的計算結(jié)果是相等的镐作。
7. 在Emacs中求值表達(dá)式
Emacs可以直接求值文本中的Lisp代碼,我們只需要將光標(biāo)定位到列表尾部隆箩,
然后按快捷鍵C-x C-e
即可该贾。(指的是按Ctrl+x
,然后再按Ctrl+e
我們還可以試試quote
和自求值對象捌臊,1
求值為1
杨蛋,'1
求值為1
。
然而''1
卻求值為(quote 1)
,是因為''1
實際是(quote (quote 1))
逞力,
它表示用字面量方式創(chuàng)建了一個形如(quote 1)
的列表對象曙寡。
下文,我們來討論Emacs Lisp的控制結(jié)構(gòu)和基本的數(shù)據(jù)類型寇荧,
使用Lisp編程是一件有趣的事情举庶。
參考
The Roots of Lisp
Land of Lisp
Lisp in small pieces
An Introduction to Programming in Emacs Lisp
GNU Emacs Lisp Reference Manual