4

我想用 2 个局部变量执行一个函数,但是这些变量的值应该取决于某些条件。例如,假设我有 2 个变量xand y,我想在letif中交换它们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)

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

(flet ((body (x y) (cons x y)))
  (if (< x y)
      (body x y)
      (body y x)))
4

6 回答 6

9

多值绑定和值

有很多选择,其中一些已经在其他答案中指出。我认为标题中的问题(“Common Lisp 中的条件变量绑定”)是multiple-value-bindvalues的一个很好的例子。我在下面使用了不同的变量名,只是为了清楚地说明 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一样。

于 2014-08-04T15:27:25.630 回答
4

如果您不想使用progv,如 sindikat 所述,您总是可以使用类似的东西:

(defmacro let-if (if-condition then-bindings else-bindings &body body)
  `(if ,if-condition
     (let ,then-bindings
       ,@body)
     (let ,else-bindings
       ,@body)))

所以表达就像

(let-if (> x y) ((x y) (y x)) ((x x) (y y))
       (cons x y))

将扩展为:

(IF (> X Y)
(LET ((X Y) (Y X))
  (CONS X Y))
(LET ((X X) (Y Y))
  (CONS X Y)))
于 2014-08-04T10:48:52.710 回答
3

rotatef

怎么样:

CL-USER> (defvar x 2)
X
CL-USER> (defvar y 1)
Y
CL-USER> (let ((x x)    ; these variables shadow previously defined
               (y y))   ; X and Y in body of LET
           (when (> x y)
             (rotatef x y))
           (cons x y))
(1 . 2)
CL-USER> x              ; here the original variables are intact
2                       ; ^
CL-USER> y              ; ^
1                       ; ^

但是,我认为在每一个这样的实际案例中,都有更简洁的方法来解决没有宏的问题。从功能的角度来看,msandiford 的回答可能是最好的。

psetf

虽然rotatef是一种非常有效的方法(它可能会被编译为大约三个机器指令交换内存中的指针),但它并不通用。

Rainer Joswing在发布问题后不久就发布了一个很好的解决方案作为评论。令我感到羞耻的是,我psetf仅在几分钟前检查了宏,这应该是非常有效且通用的解决方案。

psetf首先评估其偶数参数,然后将评估值分配给奇数位置的变量,就像这样setf做一样。

所以我们可以写:

(let ((x x)
      (y y))
  (when (> x y)
    (psetf x y y x))
  ...)

就是这样,一个人可以有条件地将任何东西重新绑定到任何东西。我认为这比使用宏要好得多。因为:

  • 我认为这种情况并不常见。
  • 发布的答案中的一些宏重复了它们的主体代码,这可能非常大:因此你得到更大的编译文件(使用宏的价格合理,但在这种情况下不是);
  • 每个自定义宏都会让其他人更难理解代码。
于 2014-08-04T11:17:21.037 回答
2

一种解决方案是使用progv而不是let,它的第一个参数是要绑定值的符号列表,第二个参数是值列表,其余是正文。

(progv '(x y) (if (< x y) (list x y) (list y x))
  (cons x y)) ; outputs (1 . 2)
于 2014-08-04T09:47:42.537 回答
1

另一种选择可能是:

(let ((x (min x y))
      (y (max x y)))
  (cons x y))
于 2014-08-04T10:18:35.017 回答
0

我的建议是destructuring-bindor之一multiple-value-bind

如果您预计需要经常执行此操作,我建议您使用宏来生成绑定。我提供了一个可能的宏(未经测试)。

(defmacro cond-let (test-expr var-bindings &body body)
  "Execute BODY with the VAR-BINDINGS in place, with the bound values depending on 
   the trueness of TEST-EXPR.

   VAR-BINDINGS is a list of (<var> <true-value> <false-value>) with missing values 
   being replaced by NIL."

  (let ((var-list (mapcar #'car var-bindings))
        (then-values (mapcar #'(lambda (l)
                                 (when (cdr l) 
                                   (nth 1 l)))
                             var-bindings))
        (else-values (mapcar #'(lambda (l)
                                 (when (cddr l))
                                    (nth 2 l)))
                             var-bindings))
     `(destructuring-bind ,var-list
         (if ,test-expr
             (list ,@then-values)
           (list ,@else-values)))))
于 2014-08-04T17:05:07.630 回答