一直在拖延,玩Lisp,終究繞不開這道坎啊。既然如此,抖擻下精神,開始瞭。
作者首先又是抱怨、比較瞭一番lisp的宏與其他語言的區別。從形式上來講,其實所謂的標準宏就和c,java之類的類庫一樣,是事先已經被完成、驗證過的東東封裝在編譯/虛擬環境 中供coder使用(省的重復造輪子)。當然,在lisp中,宏可不單單是如此簡單,不過~管他呢(拜托,這隻是初學者教程!)在這一章節主要給大夥介紹幾個常用的宏。
條件宏
最常見的估計算是"if"瞭:
Java代碼
(if condition then-form [else-form])
(if (> 2 3) "Yup" "Nope") ==> "Nope"
(if (> 2 3) "Yup") ==> NIL
(if (> 3 2) "Yup" "Nope") ==> "Yup"
真是簡單易懂啊,不過if隻支持單行動語句,也就是說,“無論條件是否成立,都隻能做一件事,而不能做一組事”。而在真實環境中,自然會大大影響其使用面。為此就引入瞭特別造作符:PROGN
Java代碼
(if (spam-p current-message)
(progn
(file-in-spam-folder current-message)
(update-spam-database current-message)))
恩,看起來還不錯,不過,如果有更簡潔的方案呢:
Java代碼
(when (spam-p current-message)
(file-in-spam-folder current-message)
(update-spam-database current-message))
when也是一個標準宏,即使不是,定義也不麻煩:
Java代碼
(defmacro when (condition &rest body)
`(if ,condition (progn ,@body)))
它的共生兄弟unless則是對條件作瞭逆判斷:
Java代碼
(defmacro unless (condition &rest body)
`(if (not ,condition) (progn ,@body)))
似乎沒什麼神奇的啊,即沒有神秘的魔法也沒有暈人的邏輯。對頭!或許"大道至簡"可以用作解釋吧。
如果說when解決瞭執行條目的問題,那麼宏cond則是解決瞭條件條目的問題:
Java代碼
(cond
(test-1 form*)
.
.
(test-N form*))
(cond (a (do-x))
(b (do-y))
(t (do-z))) '理論上來說最後一條是default
在做條件運算時,通常都會搭配些基本的運算符:and,or,not(not其實算是函數)。
Java代碼
(not nil) ==> T
(not (= 1 1)) ==> NIL
(and (= 1 2) (= 3 3)) ==> NIL '遇到第一個nil即返回,沒有則t
(or (= 1 2) (= 3 3)) ==> T '遇到第一個t即返回,沒有則nil
循環宏
最簡單的兩個循環宏莫過於dolist和dotimes瞭:
Java代碼
(dolist (var list-form)
body-form*)
(dolist (x '(1 2 3)) (print x))
1
2
3
NIL
CL-USER> (dolist (x '(1 2 3)) (print x) (if (evenp x) (return)))
1
2
NIL '通過return截斷瞭循環
—————————–
(dotimes (var count-form)
body-form*)
(dotimes (i 4) (print i))
0
1
2
3
NIL
一個循環列表,一個根據數值遞進循環,都算定制型的循環宏。而他們都有一個"父宏"(可以這麼叫麼)–do,這玩意由於超級靈活(萬金油啊),那麼也造成瞭易用度的降低:
Java代碼
(do (variable-definition*)
(end-test-form result-form*)
statement*)
variable-definition–>(var init-form step-form)
簡單看來,需要在veriable-definition中定義循環初值,跳步幅度(沒有則默認一步),當達到end-test-form時,執行result-form,他的返回值將作為整個循環的返回值。而statement則是循環體。這裡給出瞭一個例子來證明do的靈活性:
Java代碼
(do ((n 0 (1+ n))
(cur 0 next)
(next 1 (+ cur next)))
((= 10 n) cur))
其實就是傳說中的“斐波納契數列”。在這個例子中循環體似乎並不重要,重要的是構造本身。正如前面所說,靈活的同時易用性正在喪失,例子中的括弧實在令人印象深刻啊。咱們來看個對比:
Java代碼
(do ((i 0 (1+ i)))
((>= i 4))
(print i))
(dotimes (i 4) (print i))
果然是“紅花需要綠葉襯”~
在某些情況下,循環條件都是非必需的:
Java代碼
(do () '雖然沒有條件,位置還得留著
((> (get-universal-time) *some-future-date*))
(format t "Waiting~%")
(sleep 60))
接下來要介紹的算是在lisp界頗具爭議的"強力"循環宏:loop.
dolist,dotimes雖然簡單,覆蓋面卻不廣,而do又過於靈活(=復雜?),那麼看似更趨向於中間的loop便有瞭出現的理由,縱然有不少人反對他(支持的也不少)。
看起來很簡潔:
Java代碼
(loop
body-form*)
用起來也不賴:
Java代碼
(loop
(when (> (get-universal-time) *some-future-date*)
(return))
(format t "Waiting~%")
(sleep 60))
與do比較下:
Java代碼
(do ((nums nil) (i 1 (1+ i)))
((> i 10) (nreverse nums))
(push i nums)) ==> (1 2 3 4 5 6 7 8 9 10
(loop for i from 1 to 10 collecting i) ==> (1 2 3 4 5 6 7 8 9 10)
也來實現個“斐波納契數列”:
Java代碼
(loop for i below 10
and a = 0 then b
and b = 1 then (+ b a)
finally (return a))
雖然其中用到瞭類似for,below什麼的loop專用符號,但的確看著清爽多瞭,也更符合自然語言。這是loop簇擁們的理由,也是反對者腹誹的地方"一點都不lisp".個人觀點就是,你覺著哪個爽就用哪個唄,正是"條條大路通羅馬"~當然,這裡的loop使用還隻是冰山一角,後面的章節還會詳述。
lisp編譯器自帶的標準宏自然不光這麼幾個,僅僅是摘出幾個常用的,以簡單一窺其中奧妙。那麼,在接下來的章節中,即將深入介紹–自定義宏。你準備好瞭麼?
(未完待續)