按語:我任何路邊的攝像頭下走過的時候爲「不懂編程的人」寫了這一系列文章的最後一篇,整理於此。它的前一篇是《咒語》,介紹瞭如何在 Emacs Lisp 程序的世界裏登壇做法,呼風喚雨。編程
還記得segmentfault
(defun list-map (a f) (funcall (lambda (x) (if (null x) nil (cons (funcall f x) (list-map (cdr a) f)))) (car a)))
麼?函數
當時,爲了表示把手綁起來也能用腳寫字,因此故意沒用 let
,如今能夠坦然地用 let
了,這樣可讓代碼更清晰一些:code
(defun list-map (a f) (let ((x (car a))) (if (null x) nil (cons (funcall f x) (list-map (cdr a) f)))))
這個函數能夠將函數 f
做用於 列表 a
中的每一個元素,結果爲一個列表。例如:遞歸
(list-map '(1 2 3) (lambda (x) (+ x 1)))
結果爲 (2 3 4)
get
匿名函數能夠做爲參數傳遞給 list-map
,那麼有名的函數可不能夠?試試看:io
(defun ++ (x) (+ x 1)) (list-map '(1 2 3) ++)
不行。Emacs Lisp 解釋器抱怨,++
是無效的變量。它的抱怨沒錯,++
是個函數,不是變量。雖然在邏輯上,變量與函數不用分得太清,可是 Emacs Lisp 解釋器從形式上分不清什麼是函數,什麼是變量。不過,其餘 Lisp 方言,例如 Scheme 就可以分辨出來。歸根結底,仍是 Emacs Lisp 的年代過於久遠致使。function
在 Emacs Lisp 裏,須要將上述的 list-map
表達式改爲下面這樣:匿名函數
(list-map '(1 2 3) (function ++))
或者簡寫形式:變量
(list-map '(1 2 3) #'++)
用 function
或 #'
告訴 Emacs Lisp 解釋器,後面這個符號是函數。這樣 Emacs Lisp 就能夠正確識別 ++
了。
以上,只是本文的前奏。下面咱們來思考一個更深入的問題。這個問題可能深到無止境的程度。
如今,假設 list-map
所接受的列表是一個嵌套的列表——列表中有些元素也是列表:
(list-map '(1 2 3 (4 5 6) 7 8 9) #'++)
對這個表達式進行求值,發現 list-map
失靈了,++
無法做用於列表元素 (4 5 6)
。++
只能對一個數進行增 1 運算,卻不能對一個列表這樣作。假若咱們真的很想讓 ++
可以繼續進入 (4 5 6)
內部,將其中每個元素都增 1,而後再跳出來繼續處理 (4 5 6)
後面的元素,該怎麼辦?
首先,咱們須要具備判斷列表中的一個元素是否是列表的能力。Emacs Lisp 提供的 listp
函數可讓咱們具備這種能力。例如:
(listp 3) (listp '[1 2 3]) (listp '(1 2 3)) (listp '()) (listp nil)
上面這五個表達式,前兩個的求值結果皆爲 nil
,後面三個的求值結果皆爲 t
。
有了 listp
,咱們就能夠區分一個列表元素是原子仍是列表了。能區分,就好辦。假若列表元素依然是列表,那麼咱們就繼續將 list-map
做用於這個元素,而假若它不是列表,那麼就用 ++
之類的函數伺候之。
試試看:
(defun list-map (a f) (let ((x (car a))) (if (null x) nil (if (listp x) (cons (list-map x f) (list-map (cdr a) f)) (cons (funcall f x) (list-map (cdr a) f))))))
試驗一下這個新的 list-map
能不能用:
(list-map '(1 2 3 (4 5 6) 7 8 9) #'++)
結果獲得 (2 3 4 (5 6 7) 8 9 10)
,正確。
再拿更多層數的列表試試看:
(list-map '(1 2 3 (4 5 (0 1)) 7 8 9 (3 3 3)) #'++)
結果獲得 (2 3 4 (5 6 (1 2)) 8 9 10 (4 4 4))
,正確。
就這樣,咱們只是對 list-map
略動手腳,彷佛就可讓不管嵌套有多少層,藏匿有多深的列表,在 list-map
面前都是盡收眼底的。
在未對列表結構有任何破壞的狀況下,能夠肯定上述的感受是正確的。由於計算機的運轉老是周而復始。假若程序自己只變更了數據的形狀,而未破壞它的拓撲結構,咱們就老是可以作到見微而知著。
上面對 list-map
的修改,雖然只考慮了再次使用 list-map
來處理列表元素爲列表的狀況,結果卻讓 list-map
可以適用於任何形式的列表嵌套。咱們在用宏的形式定義 my-let*
的時候也遇到過這樣的狀況。爲何會這樣?這實際上是在周而復始的運動中,出現了類型。listp
可以判斷一個值是不是列表類型。在一個 Emacs Lisp 程序裏,能夠有無數個列表,但它們的類型倒是相同的,都是列表類型。
天網恢恢,疏而不漏,靠的不過是遞歸 + 類型。類型,描述了值的共性。它生活在柏拉圖的理想國裏,是一種完美的模具,而那些值只不過是從模具裏鑄出來的東西。類型是比遞歸一個更大的題目,已經有許多人寫了這方面的專著。假若你對這個感興趣,能夠經過 Haskell 語言瞭解這方面的一些概念。
下一篇:
'()