更过程式的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表达式便结束了。

全文完

阅读原文

相关文章
相关标签/搜索