做爲一名自誇的non-trivial的Common Lisp程序員,在編碼的時候常常會遇到使人不愉快的地方,其中一個即是LET。git
一段典型的LET
的示例代碼以下程序員
(let ((a 1)) a)
大多數時候,LET
不會只有一個綁定。而且,也不會只是綁定一個常量這麼簡單,而應當是下面這樣的github
(let ((a (foo x y)) (b (bar z))) (function1 a b) (function2 a b))
有時候我會想看看某一個綁定的值——最好是在它計算完畢後當即查看。若是要查看foo
函數的返回值,能夠這樣寫算法
(let ((a (foo x y)) (b (bar z))) (print a) (function1 a b) (function2 a b))
若是調用foo
和bar
都順利的話上面的代碼也就夠了。比較棘手的狀況是,若是a
的值不符合預期,會致使b
的計算過程出情況(儘管在上面的代碼中看似不會)。這種狀況多出如今LET*
的使用中,以下面所示函數
(let* ((a (foo x y)) (b (bar a))) (function1 a b) (function2 a b))
若是錯誤的a
會致使bar
的調用出錯,那麼在調用function1
以前才調用print
打印a
已經爲時過晚了——畢竟調用bar
的時候就拋出condition
往調用鏈的上游走了。一種方法是寫成下面這樣子編碼
(let* ((a (let ((tmp (foo x y))) (print tmp) tmp)) (b (bar a))) (function1 a b) (function2 a b))
這也太醜了!要否則寫成下面這樣子?code
(let ((a (foo x y))) (print a) (let ((b (bar a))) (function1 a b) (function2 a b)))
原本一個LET
就能夠作到的事情,這下子用了兩個,還致使縮進更深了一級。若是有十個變量須要打印,就會增長十個LET
和十層縮進。若是心血來潮想查看一個變量的值,還要大幅調整代碼。orm
問題大概就出在LET
和LET*
的語法上。以LET
爲例,它由截然分開的bindings
和forms
組成,二者不能互相穿插。所以,若是想在bindings
中求值一段表達式,只能將bindings
切開,寫成兩個LET
的形式。好像寫一個新的宏能夠解決這個問題?是的,vertical-let
就是。htm
vertical-let
是一個我本身寫的宏,源代碼在此。其用法以下get
(vertical-let :with a = 1 a)
它借鑑了LOOP
中綁定變量的方式(即:with
和=
),綁定變量和用於求值的代碼還能夠交織在一塊兒,以下
(vertical-let :with a = 1 (print a) :with b = 2 (+ a b))
vertical-let
最終會展開爲LET
,好比上面的代碼,會展開爲以下的代碼
(LET ((A 1)) (PRINT A) (LET ((B 2)) (+ A B)))
vertical-let
的算法很簡單。它遍歷表達式列表,當遇到:with
時就把接下來的三個元素分別視爲變量名、等號,以及待求值的表達式,將三者打包進一個列表中,再壓棧;當遇到其它值時,就視爲待求值的表達式(將會組成LET
的forms
部分),也放進列表中再壓棧(具體方法參見源代碼)。
將全部值都遍歷並壓棧後,接下來要遍歷這個棧中的元素。先準備兩個空的棧——一個存放bindings
,一個存放forms
。接着,對於每個從棧中彈出的元素,分爲以下兩種狀況:
binding
,則直接壓入存放bindings
的棧,不然;binding
,則說明已經有一段完整的LET
的內容被集齊。所以,將目前在兩個棧中的內容所有彈出,組合爲一個LET
表達式再壓入存放forms
的棧中。而後將方纔彈出的表達式也壓入forms
。重複上述過程直至全部元素都被處理,最後將還在兩個棧中的內容也組合爲一個LET
表達式便結束了。全文完
【閱讀原文】