一个名称的多个变量或Lisp中一个变量的多个绑定

时间:2013-09-12 11:55:10

标签: variables lisp

我对如何使用术语变量或绑定感到困惑/不确定。我认为我的不确定性可归结为三个相关的简单问题。

(let ((hi 'outer))
  (let ((hi 'inner))
    (print hi))
  (print hi))

问题A:在上面的代码中,以下哪项是正确的?

  1. 只有一个变量。一个变量有两个绑定:外部绑定和内部绑定。

  2. 一个名称有两个变量:外部变量和内部变量。

  3. 当我在互联网上阅读有关局部变量的文章时,有时文章似乎选择1,有时候2.两者是否同样正确?

    (let ((hi 0))
      (print hi)
      (setq hi 1)
      (print hi)
      (setq hi 2)
      (print hi))
    

    问题B:以下哪项适合上述代码?

    1. 有一个正在重复使用的绑定。

    2. 有三种绑定。

    3. 我从未见过任何使用单词绑定的材料,选择2作为答案,但另一方面,人们仍然可能会认为“hi这个名字被绑定了三次。发生了三次绑定。代码确实三个绑定。“所以我不确定。

      (defun fac (n)
        (if (> n 1)
            (* n (fac (1- n)))
          1))
      (fac 4)
      

      问题C:正在执行递归函数时,哪个是对的?

      1. 一个变量会同时绑定几个。

      2. 同时会有多个名称的变量。

      3. 这似乎与问题A类似,但问题A涉及两个let表单,每个表单只执行一次,而这个问题更像是一个let表单,它同时在多个实例中执行。

        这些问题是针脚的角度吗?我想知道这些问题,因为有很多关于在循环中使用闭包的着名问题的文章,我认为理解这些文章需要知道一个变量是什么以及绑定是什么。

2 个答案:

答案 0 :(得分:9)

根据Common Lisp glossary :(其他Lisps在术语上可能有所不同)

  • variable:“变量”命名空间中的绑定。
  • binding:名称与名称所代表的名称之间的关联。
  • assign:更改已建立的绑定中的变量值。

所以答案是:

  • A:两个变量(和两个绑定)
  • B:一次绑定(分配两次)
  • C:一个名称的几个绑定(和几个变量)

答案 1 :(得分:3)

我认为Lars Brinkhoff's answer最直接地通过吸引HyperSpec来回答这个问题。您也可以查看Peter Seibel的 Practical Common Lisp 中的Chapter 6. Variables

但是,我们还要考虑我们可以做些什么来测试这个?具有词法作用域和词法闭包的语言的一个优点是可以在闭包之间共享相同的绑定。

多个闭包引用的一个绑定

例如,我们可以绑定一个变量x(毫无疑问,这里只有一个x并返回两个访问它的闭包:

(defun incrementer-and-getter (value)
  (let ((x value))
    (values (lambda ()
              (setq x (+ 1 x)))
            (lambda () 
              x))))

然后,当我们使用闭包时,我们可以看到它们引用相同的绑定:

(multiple-value-bind (inc get) 
    (incrementer-and-getter 23)
  (list (funcall get)
        (funcall inc)
        (funcall get)))
; => (23 24 24)

嵌套let s

的多个绑定

现在我们可以做类似的事情来测试你给出的案例中有多少个绑定:

(defun test2 ()
  (let (get-outer
        set-outer
        get-inner
        set-inner)
    (let ((hi 'outer))
      (setq get-outer (lambda () hi)
            set-outer (lambda (new) (setq hi new)))
      (let ((hi 'inner))
        (setq get-inner (lambda () hi)
              set-inner (lambda (new) (setq hi new)))))
    (values get-outer
            set-outer
            get-inner
            set-inner)))

(multiple-value-bind (get-outer set-outer get-inner set-inner)
    (test2)
  (list (funcall get-outer)             ; retrieve outer
        (funcall get-inner)             ; retrieve inner
        (funcall set-outer 'new-outer)  ; update outer
        (funcall set-inner 'new-inner)  ; update inner
        (funcall get-outer)             ; retrieve outer
        (funcall get-inner)))           ; retrieve inner
; => (OUTER INNER NEW-OUTER NEW-INNER NEW-OUTER NEW-INNER)

内部和外部绑定不同。

使用setq

更新的单个绑定

现在有多个setq案例:

(defun test3 ()
  (let (get-first
        set-first
        get-second
        set-second)
    (let ((hi 'first))
      (setq get-first (lambda () hi)
            set-first (lambda (new) (setq hi new)))
      (setq hi 'second)
      (setq get-second (lambda () hi)
            set-second (lambda (new) (setq hi new))))
    (values get-first
            set-first
            get-second
            set-second)))
(multiple-value-bind (get-first set-first get-second set-second)
    (test3)
  (list (funcall get-first)
        (funcall get-second)
        (funcall set-first 'new-first)
        (funcall get-first)
        (funcall get-second)
        (funcall set-second 'new-second)
        (funcall get-first)
        (funcall get-second)))
    (multiple-value-bind (get-first set-first get-second set-second)
        (test3)
      (list (funcall get-first)
            (funcall get-second)
            (funcall set-first 'new-first)
            (funcall set-second 'new-second)
            (funcall get-first)
            (funcall get-second)))
; => (SECOND SECOND NEW-FIRST NEW-FIRST NEW-FIRST NEW-SECOND NEW-SECOND NEW-SECOND)

此处,get-firstget-second都返回相同的值,set-firstset-second都会更新该值。闭包靠近相同的绑定。

每次调用函数都会建立 new 绑定

对于递归案例,我们必须有点偷偷摸摸,但我们仍然可以检查:

(defparameter *closures* '())

(defun recurse (n)
  (push (lambda () n) *closures*)
  (push (lambda (new) (setq n new)) *closures*)
  (unless (zerop n)
    (recurse (1- n))))

(recurse 1) ; put four closures into *closures*

现在我们可以把它们拿出去看看会发生什么:

;; remember we pushed these in, so they're in backwards
;; order, compared to everything else we've done.
(destructuring-bind (set-y get-y set-x get-x)
    *closures*
  (list (funcall get-x)
        (funcall get-y)
        (funcall set-x 'new-x)
        (funcall set-y 'new-y)
        (funcall get-x)
        (funcall get-y)))
; => (1 0 NEW-X NEW-Y NEW-X NEW-Y)

每次调用函数都有一个新的绑定,因此闭包引用了不同的绑定。

迭代构造中常见的混淆

对于它的价值而言,习惯这种行为并不太难(如果它首先出乎意料的话)。然而,即使是经验丰富的Lisp老兵也可能会在迭代结构中绊倒行为。这种情况突然使得知道例如do是否为每次迭代建立新的绑定,或者它是否更新相同的绑定非常重要。下列代码应该打印什么,109876543210000000000

(let ((closures '()))
  (do ((x 10 (1- x)))
      ((zerop x))
    (push (lambda () x) closures))
  (map nil (lambda (closure)
             (print (funcall closure)))
       closures))

对于do,它是0000000000,因为(强调添加):

  

所有步骤形式(如果提供)将从左到右进行评估,结果值分配到相应的变量。

这在loop中出现了很多,它依赖于实现,但是人们期望与其他迭代宏有不同的行为。例如,请参阅此StackOverflow问题:

comp.lang.lisp上的这些主题: