更過程式的let——vertical-let

做爲一名自誇的non-trivial的Common Lisp程序員,在編碼的時候常常會遇到使人不愉快的地方,其中一個即是LETgit

一段典型的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))

若是調用foobar都順利的話上面的代碼也就夠了。比較棘手的狀況是,若是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

問題大概就出在LETLET*的語法上。以LET爲例,它由截然分開的bindingsforms組成,二者不能互相穿插。所以,若是想在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時就把接下來的三個元素分別視爲變量名、等號,以及待求值的表達式,將三者打包進一個列表中,再壓棧;當遇到其它值時,就視爲待求值的表達式(將會組成LETforms部分),也放進列表中再壓棧(具體方法參見源代碼)。

將全部值都遍歷並壓棧後,接下來要遍歷這個棧中的元素。先準備兩個空的棧——一個存放bindings,一個存放forms。接着,對於每個從棧中彈出的元素,分爲以下兩種狀況:

  1. 若是表示binding,則直接壓入存放bindings的棧,不然;
  2. 若是是待求值的表達式,而且上一個出棧的元素是binding,則說明已經有一段完整的LET的內容被集齊。所以,將目前在兩個棧中的內容所有彈出,組合爲一個LET表達式再壓入存放forms的棧中。而後將方纔彈出的表達式也壓入forms。重複上述過程直至全部元素都被處理,最後將還在兩個棧中的內容也組合爲一個LET表達式便結束了。

全文完

閱讀原文

相關文章
相關標籤/搜索