Common Lisp中的条件变量绑定

Mir*_*lov 3 lisp common-lisp conditional-binding

我想用2个局部变量执行一个函数,但这些变量的值应该取决于某些条件.举例来说,假设我有2个变量xy,我想里面交换他们let如果y > x.交换应该是暂时的,我不想改变状态rotatef.我的代码看起来像:

(setq x 2)
(setq y 1)
(let (if (> x y) ((x y) (y x)) ((x x) (y y)))
  (cons x y)) ; should return (1 . 2)
Run Code Online (Sandbox Code Playgroud)

但是里面的表达式let并不是有效的Lisp.如何有条件地为本地变量赋值?解决方法是将主体放入flet并使用不同的参数调用它,但它看起来很笨拙:

(flet ((body (x y) (cons x y)))
  (if (< x y)
      (body x y)
      (body y x)))
Run Code Online (Sandbox Code Playgroud)

Jos*_*lor 8

多值绑定和值

有很多替代方案,其中一些已在其他答案中指出.我认为标题中的问题("Common Lisp中的条件变量绑定")是多值绑定值的一个很好的例子.我在下面使用了不同的变量名,只是为了清楚说明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)
Run Code Online (Sandbox Code Playgroud)

然后,使用一些宏观,我们可以使它更清洁,就像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)))
Run Code Online (Sandbox Code Playgroud)

(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))
Run Code Online (Sandbox Code Playgroud)

(let ((a 3) (b 2))
  (if-let (< a b)
      ((x a b)
       (y b a))
    (cons x y)))
;=> (2 . 3)
Run Code Online (Sandbox Code Playgroud)

与progv比较

在使用方面,这与sindikat的答案有一些相似之处,但是多值绑定会像let一样建立绑定:默认情况下是词法,但全局或局部特殊声明会使绑定动态化.另一方面,progv建立动态绑定.这意味着如果绑定完全由progv引入,您将看不到太大的区别(除了尝试返回闭包),但是您不能影子绑定.我们可以看到这一点而无需进行任何有条件的工作.这是两个示例代码段.在第一个中,我们看到x的内部引用实际上是指词法绑定,而不是由progv建立的动态绑定.要引用progv建立的那个,实际上需要声明内部引用是特殊的. progv不接受声明,但我们可以在本地使用.

(let ((x 1))
  (progv '(x) '(2)
    x))
;=> 1
Run Code Online (Sandbox Code Playgroud)

(let ((x 1))
  (progv '(x) '(2)
    (locally (declare (special x))
      x)))
;=> 2
Run Code Online (Sandbox Code Playgroud)

multiple-value-bind实际上以我们期望的方式进行绑定:

(let ((x 1))
  (multiple-value-bind (x) (values 2)
    x))
;=> 2
Run Code Online (Sandbox Code Playgroud)

最好使用像multiple-value-bind这样的绑定构造,默认情况下建立词法绑定,就像let一样.