多值绑定和值
有很多选择,其中一些已经在其他答案中指出。我认为标题中的问题(“Common Lisp 中的条件变量绑定”)是multiple-value-bind和values的一个很好的例子。我在下面使用了不同的变量名,只是为了清楚地说明 x 和 y 的位置,以及原始值的来源。但是,名称可以相同;这只是将他们隐藏在里面。
(let ((a 3)
(b 2))
(multiple-value-bind (x y)
(if (< a b)
(values a b)
(values b a))
(cons x y)))
;=> (2 . 3)
然后,使用一点宏观,我们可以让它更干净一点,就像coredump 所做的那样:
(defmacro if-let (test bindings &body body)
"* Syntax:
let ({var | (var [then-form [else-form]])}*) declaration* form* => result*
* Description:
Similar to LET, but each binding instead of an init-form can have a
then-form and and else-form. Both are optional, and default to NIL.
The test is evaluated, then variables are bound to the results of the
then-forms or the else-forms, as by LET."
(let ((bindings (mapcar #'(lambda (binding)
(destructuring-bind (variable &optional then else)
(if (listp binding) binding (list binding))
(list variable then else)))
bindings)))
`(multiple-value-bind ,(mapcar 'first bindings)
(if ,test
(values ,@(mapcar 'second bindings))
(values ,@(mapcar 'third bindings)))
,@body)))
(pprint (macroexpand-1 '(if-let (< x y) ((x x y)
(y y x))
(cons x y))))
; (MULTIPLE-VALUE-BIND (X Y)
; (IF (< X Y)
; (VALUES X Y)
; (VALUES Y X))
; (CONS X Y))
(let ((a 3) (b 2))
(if-let (< a b)
((x a b)
(y b a))
(cons x y)))
;=> (2 . 3)
与 progv 的比较
在使用方面,this 与sindikat 的 answer有一些相似之处,但multi -value-bind建立绑定就像let一样:默认情况下是词法,但是全局或局部的特殊声明会使绑定动态化。另一方面,progv建立动态绑定。这意味着如果绑定完全由progv引入,您不会看到太大的区别(除了尝试返回闭包),但您不能隐藏绑定。我们完全不需要做任何有条件的工作就可以看到这一点。这是两个示例片段。首先,我们看到对 x 的内部引用实际上是指词法绑定,而不是progv建立的动态绑定。要引用progv建立的那个,你实际上需要声明内部引用是特殊的。 progv不接受声明,但我们可以使用local。
(let ((x 1))
(progv '(x) '(2)
x))
;=> 1
(let ((x 1))
(progv '(x) '(2)
(locally (declare (special x))
x)))
;=> 2
多值绑定实际上按照我们期望的方式进行绑定:
(let ((x 1))
(multiple-value-bind (x) (values 2)
x))
;=> 2
使用像multi-value-bind这样的绑定结构可能会更好,它默认建立词法绑定,就像let一样。