lisp - Common Lisp 中的条件变量绑定(bind)

标签 lisp common-lisp conditional-binding

我想执行一个带有 2 个局部变量的函数,但是这些变量的值应该取决于某些条件。例如,假设我有 2 个变量 xy,我想在 let if 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)))

最佳答案

多值绑定(bind)和值

有很多选择,其中一些已经在其他答案中指出。我认为标题中的问题(“Common Lisp 中的条件变量绑定(bind)”)是 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 did :

(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的比较

在使用上,这与sindikat's answer有一些相似之处, 但 multiple-value-bind 建立绑定(bind)就像 let 一样:默认是词法的,但全局或局部特殊声明将使绑定(bind)动态化。另一方面,progv建立动态 绑定(bind)。这意味着如果绑定(bind)完全由 progv 引入,您将看不到太大差异(除了尝试返回闭包),但您不能隐藏绑定(bind).我们根本不需要做任何有条件的工作就可以看到这一点。这是两个示例片段。首先,我们看到对 x 的内部引用实际上是指词法绑定(bind),而不是 progv 建立的动态绑定(bind)。要引用progv建立的那个,其实需要声明内部引用是特殊的。 progv 不接受声明,但我们可以在本地使用。

(let ((x 1))
  (progv '(x) '(2)
    x))
;=> 1

(let ((x 1))
  (progv '(x) '(2)
    (locally (declare (special x))
      x)))
;=> 2

multiple-value-bind 实际上按照我们期望的方式进行绑定(bind):

(let ((x 1))
  (multiple-value-bind (x) (values 2)
    x))
;=> 2

最好使用像 multiple-value-bind 这样的绑定(bind)结构,它默认建立词法绑定(bind),就像 let 一样。

关于lisp - Common Lisp 中的条件变量绑定(bind),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25115876/

相关文章:

小块的 Lisp。第1章

lisp - 尝试反转列表时出现段错误

swift - 理解没有返回类型的闭包语法

lisp - 使用 Lisp 重印列表

ruby - Ruby 中有哪些 Lisp 的特性?

list - 方案:列表的CAR和CDR

macros - SBCL Common Lisp 实现中何时以及多久发生一次宏扩展?

lisp - 访问整数位的函数的 Common Lisp setf 扩展

swift - 有条件地绑定(bind)到现有属性

string - 条件绑定(bind)的初始化程序必须具有 Optional 类型,而不是 HTTPCookieStorage 上的 'String'