我初學 Emacs Lisp 時,一直搞不懂 Lisp 裡面的 lambda
到底是什麼東西。多年後才開竅,豁然開朗感覺真是很神奇。
以下會拿 Emacs Lisp 跟 Scheme 當例子。
匿名函數
簡單來說,Lisp 裡的 lambda
就是沒有名字的函數。精確的說,lambda
是 Lisp 中拿來表示「匿名函數」(anonymous function) 的方法。
…這種說法誰聽得懂啦。那麼先來用大家在 Common Lisp / Emacs Lisp 中最熟悉的 defun
定義一個函數 double
,它接受 x
一個參數,最後回傳 x * 2
的值:
1 | ;;;; Emacs Lisp / Common Lisp |
但如果寫過 Scheme,就會知道 Scheme 可以寫成等價的:
1 | ;;;; Scheme |
注意第二種寫法,他其實就是把 double
這個名稱綁到 (lambda (x) (* x 2))
上面,所以以後我們呼叫 (double ...)
就是在呼叫 (lambda (x) (* x 2))
相較於 Scheme 的設計,Emacs Lisp 的 defun
在背後做了很多 magic,而不像 Scheme 這樣極簡主義、沒有什麼 magic 直接暴露給使用者看內部設計。其實呢…Emacs Lisp 中,defun
所做的事情跟 Scheme 的 define
本質上其實一模一樣,裡面也是一個 lambda
,一樣也是把這個 lambda
綁到一個名子上:
1 | ;; 因為 defun 在 Emacs Lisp 中其實只是一個 macro, |
呼叫 Lambda
上面我們用了 defun 或 define 給原本沒有名子的 lambda 函數取了名子 double
,可以讓我們輕鬆的使用:
1 | (double 7) |
這種方法來呼叫實際上長成這樣的 lambda 函數:
1 | (lambda (x) (* x 2)) |
最後回傳:
1 | ;; => (* x 2) |
那,我們要怎麼呼叫一個沒有名字的 lambda 函數呢?很簡單,根據 Lisp 的語法,僅僅寫一個寫 car
只會被 interpreter 解釋成「取出 car
這個變數的值」(所以身為 LISP-1 的 Scheme interpreter 會回你 #<procedure car (_)>
,而身為 LISP-2 的 Emacs Lisp interpreter 則會爆炸,因為你還沒有定義 car
這個變數),但(car x)
就會變成要執行該變數。如果 car
這個變數的內容是一個 (lambda ...)
表達式,就會變成執行這個 lambda
。也就是說,讓你的 lambda 包上一層括弧,這個 lambda 就會被執行了:
1 | (lambda (x) (* x 2)) ;; 就只是一個「沒有名字」的函數,沒有被呼叫。 |
看得懂嗎?GNU Emacs Lisp 文件就是這樣教你 lambda 的,但當時初學 Lisp,腦子打結完全看不懂,兩年後把才突然開竅,原來這麼簡單、又神奇。只是一眼看過去被括號混淆了視聽而造成理解障礙而已。
LISP-1 與 LISP-2?
LISP-1 是指 function 與 variable 共用同一個 namespace 的語言,所以你沒辦法讓 function 與 varianble 取同個名字。目前大部分的程式語言都是這樣設計,例如 Python、Java、JavaScript、Scheme。
LISP-2 則是 function 與 variable 各自有不同 namespace 的語言,所以你可以同時定義一個名字叫
foobar
的 function,以及一個叫foobar
的 variable。這樣設計的語言有 Ruby、Emacs Lisp、Common Lisp、Perl。
呼叫 Lambda 的其他方式
funcall
與 apply
因為 Emacs Lisp 是 LISP-2,不像 Scheme 那樣 LISP-1 單純,所以你沒辦法直接用 setq
來把 lambda 綁到一個變數上直接呼叫。不過還是可以用 funcall
或 apply
來強制呼叫 variable 裡的 lambda
其實他們原本的主要用途並不是這樣啦…只是剛好這裡可以這樣做。
1 | (setq multiply (lambda (a b) (* a b))) |
高階函數
Lisp 裡面是非常常用 Lambda 的,例如最常用的 mapcar
1
2
3(mapcar (lambda (x) (* x 2))
'(1 2 3 4 5))
;; => (2 4 6 8 10)
或者也很常用的 remove-if
1 | (remove-if (lambda (x) (> x 5)) |
關於 Lambda 一些有趣的事情
Lambda calculus 又是個更難懂的東西,我自己也還沒仔細去看過。不過前陣子有大大跟我說「let
是可以很簡單的轉換成 lambda
的」:
1 | ;;; let |
原來 let 的本質這麼簡單,美得看到當下真是嚇了一跳。有時間來讀一下 lambda calculus 吧。