Emacs之魂(三):列表,引用和求值策略
Emacs之魂(一):開篇
Emacs之魂(二):一分鐘學會人界用法
Emacs之魂(三):列表,引用和求值策略
Emacs之魂(四):標識符,符號和變量
Emacs之魂(五):變量的“指針”語義
Emacs之魂(六):宏與元編程
Emacs之魂(七):變量捕獲與衛生宏
Emacs之魂(八):反引用與嵌套反引用
Emacs之魂(九):讀取器宏
- -
回顧
上文我們介紹了Emacs的用法,發現一分鐘學會使用它并不是難事,
而且,我們沒有讓快捷鍵束縛住,因為Emacs的精髓在于Emacs Lisp中。
本文我們開始探討Emacs Lisp,不過在這之前我們還要先熟悉一下Lisp的特點和Lisp家族的成員,
隨后本文重點分析和介紹了列表,引用和求值策略,
這幾個概念,尤其是引用,對學習者來說非常容易引起困惑,
本文采用了不同的角度來描述這些概念。
1. 強大的Lisp
1960年,John McCarthy發表了一篇計算機領域的文章,這是一篇“驚世之作”,
它的作用簡直就像歐幾里得《幾何原本》對幾何學的貢獻一樣。
John McCarthy只用了一些簡單的的運算符和函數,構建出了一門圖靈完備的編程語言,
稱之為Lisp,Lisp是列表處理(List Processing)的簡稱。
這門語言的關鍵思想是,不論代碼還是數據,都用統一的數據結構(列表)進行表示。
Lisp語言具有很強的表達能力,我們可以用更少的代碼做更多的事情。
通常而言,語言具有表達能力就必須提供豐富的內置功能和強大的擴展性。
語言的內置功能指的是語言默認提供的功能,它能減少程序員的重復勞動,幫助他們快速完成工作。
語言的擴展性,指的是當語言內置功能不能滿足需要的時候,程序員可以怎樣做。
同時具有豐富的功能和強大的擴展性是很困難的,這需要在語言的設計階段就考慮好,
語言的內置功能越多,就會越復雜,擴展功能的與內置功能的一致性就很難被保證。
現代的高級編程語言,離不開編譯器和解釋器,
編譯器將高級語言的代碼轉換成更底層的語言,例如C語言或者匯編,
解釋器提供了一個運行時環境,直接解釋執行高級語言的源代碼。
一般而言,編譯器是由語言的開發商提供的,使用者并不會參與到編譯器的開發工作之中,
如果想要在語言中支持一等函數(first-class function),就必須讓語言的開發商改寫編譯器,
如果需要增加新的類似if-else的控制結構,或者讓語言支持面向對象編程,也要改寫編譯器才行。
因此,語言支持什么功能,以及源代碼被如何編譯,完全取決于開發商。
而Lisp語言則不同,它允許程序員對編譯器進行編程,(元編程
Lisp程序員可以決定代碼被如何編譯甚至如何被讀取,像是半個編譯器的開發者一樣。
2. Lisp-1和Lisp-2
Lisp語言構成了一個家族,具有成百上千種方言,
用的最多的幾種是,Common Lisp,Scheme,Racket,和Emacs Lisp。
其中Scheme的目標是簡潔,Common Lisp提供了強大的工業級支持,
Racket提供了一種創造語言和設計實踐的平臺,Emacs Lisp主要用于Emacs中。
Emacs Lisp更像Common Lisp,它們都是Lisp-2,
即同一個符號在不同的上下文中,可以分別用來表示變量和函數,
而Scheme和Racket則只能用來表示同一個實體,稱為Lisp-1。
出現Lisp-2,主要是因為有效率方面的考慮,
在Lisp-2中,函數和變量分屬不同的名字空間,在不同的環境中,由不同的求值器進行處理。
這樣做也使語義更加復雜了,以后的文章中,我們會介紹Emacs Lisp中符號(Symbol)的概念。
3. 列表對象和它的文本表示
列表是Lisp語言中一種常用的數據結構,用來表示一批數據。
例如,由整數1,2和3構成的列表對象,Lisp會將它打印為,(1 2 3)。
各個列表元素用空格分隔,用圓括號括起來。
然而,在Lisp代碼中直接寫(1 2 3),并不會創建一個列表對象,
因為Lisp程序也是用括號方式表示的,例如,(+ 1 2)表示對整數1和2進行加法運算。
那么如何才能創建一個列表對象呢?
我們需要調用list函數,(list 1 2 3),這段代碼將會創建一個由整數1,2和3構成的列表對象,
這個列表對象打印為(1 2 3)。
注意,以上我們嚴謹的區分了Lisp對象和它的打印結果,
是因為對象和它的文本表示(textual representation)是不同的概念。
例如,在C語言中我們寫,
int result = 1 + 2;我們實際上是用“1”表示了整數1,“1”只是一段文本,是印刷符號,而整數1是一個數學對象,
同樣的,“+”是一段文本,它表示了加法運算符。
在Lisp語言中,我們用文本來寫程序,而Lisp讀取器得到的是Lisp對象,
經過對這些Lisp對象進行計算,得到了計算結果,也是一個Lisp對象,
最終,反饋給我們的是這個對象的文本表示。
4. 字面量和引用
在Lisp中,我們用文本“1”可以直接表示整數1,用“#t”表示真值,
類似的“1”和“#t”,稱之為對象的字面量表示(literal representation)。
其它語言中,也提供了廣泛的字面量表示法,例如,JavaScript提供了數組和對象字面量,
const obj = {x: 1,y: [2, 3, 4] };這段代碼創建了一個JavaScript Object,它的x屬性值是1,y屬性值是一個數組。
字面量表示法,使得我們不必調用new Object和new Array來創建它。
Lisp中列表對象用的非常多,每次都使用list函數來創建是一件麻煩的事情,
因此,Lisp語言提供了列表對象的字面量寫法,我們只需要調用quote就可以了。
以上Lisp代碼會創建一個打印形式為(1 2 3)的列表對象。
對于嵌套列表,使用quote是非常方便的,
(quote (1 (2 3) ((4 5) (6 7))))像這樣創建列表的方式,稱為引用(quoting),
這不同于按引用調用(call by reference)中的“引用”(reference)。
quote還有一個便捷的寫法,就是用單引號來表示它,(quote (1 2 3))可以表示為,
'(1 2 3)我們只需要在列表前加一個單引號即可,因為列表的右括號表明了它在引用這個列表。
5. quote和list
值得一提的是,引用并不保證每次都會重新創建列表。
例如,在Emacs Lisp中我們使用defun創建函數,
(defun foo ()'(1 2 3))然后,用以下方式進行函數調用,注意foo參數個數為0個,
(foo)多次調用foo,編譯器可能返回同一個列表對象。
而list則不同,每次調用它會返回一個全新的列表對象,
(defun foo ()(list 1 2 3))6. 求值策略
Lisp代碼是由表達式構成的,Lisp程序的執行過程就是表達式的求值過程,
(* (+ 1 2) (+ 3 4))以上表達式的求值結果為21。
在程序的列表表示法中,從左到右位于第一個位置的元素,是比較特殊的,
它表示一個函數(function),一個宏(macro),或者一個內置的特殊命令(special form)。
位于其他位置的元素稱為參數。
函數被調用的時候,它的每個參數都必須首先被求值,
例如,以上程序中*,+都是函數,
在調用乘法函數*時,它的參數(+ 1 2)和(+ 3 4)都首先要被求值,分別求值為3和7,
然后再進行乘法運算,結果為21。
而宏和內置的特殊命令,并不要求如此,它們有自己的對參數的求值策略。
其中“1”稱之為自求值對象,對它進行求值將得到它本身,
1 (eval 1) (eval (eval 1))其結果都為1,其它的自求值對象還包括布爾值,字符串,向量,等等。
(+ 1 2)中1和2之前沒有quote,是因為它們是自求值對象,(+ '1 '2)和(+ 1 2)的計算結果是相等的。
7. 在Emacs中求值表達式
Emacs可以直接求值文本中的Lisp代碼,我們只需要將光標定位到列表尾部,
然后按快捷鍵C-x C-e即可。(指的是按Ctrl+x,然后再按Ctrl+e
我們還可以試試quote和自求值對象,1求值為1,'1求值為1。
然而''1卻求值為(quote 1),是因為''1實際是(quote (quote 1)),
它表示用字面量方式創建了一個形如(quote 1)的列表對象。
下文,我們來討論Emacs Lisp的控制結構和基本的數據類型,
使用Lisp編程是一件有趣的事情。
參考
The Roots of Lisp
Land of Lisp
Lisp in small pieces
An Introduction to Programming in Emacs Lisp
GNU Emacs Lisp Reference Manual
總結
以上是生活随笔為你收集整理的Emacs之魂(三):列表,引用和求值策略的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: libxxx.so- text relo
- 下一篇: Spring Cloud微服务分布式云架