4

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

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

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

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

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

我在网上看局部变量的时候,有时候文章好像选1,有时候选2,这两个是不是一样对?

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

问题 B:对于上述代码,下列哪项是正确的?

  1. 有一个绑定正在被重用。

  2. 有三个绑定。

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

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

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

  1. 一个变量将同时有多个绑定。

  2. 同一时间会有多个同名变量。

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

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

4

2 回答 2

9

根据Common Lisp 词汇表:(其他 Lisp 可能会或可能不会在术语上有所不同)

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

所以答案是:

  • A:两个变量(和两个绑定)
  • B:一个绑定(被分配两次)
  • C:一个名称的多个绑定(和多个变量)
于 2013-09-12T12:30:30.297 回答
3

我认为Lars Brinkhoff 的回答通过诉诸 HyperSpec 最直接地回答了这个问题。您还可以查看第 6 章。Peter Seibel 的Practical Common Lisp中的变量

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

多个闭包引用的一个绑定

例如,我们可以绑定一个变量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)

嵌套lets 的多个绑定

现在我们可以做一些类似的事情来测试在你给出的情况下有多少绑定:

(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-first返回get-second相同的值,并且两者都set-first更新set-second该值。闭包在同一个绑定上关闭。

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

对于递归的情况,我们必须稍微偷偷摸摸,但我们仍然可以检查一下:

(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为每次迭代建立一个新的绑定,或者是否更新相同的绑定。下面的代码应该打印什么, 10987654321或者0000000000

(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

于 2013-09-12T12:55:58.917 回答