【问题标题】:Multiple variables of one name or multiple bindings of one variable in LispLisp中一个名称的多个变量或一个变量的多个绑定
【发布时间】:2013-09-12 11:55:09
【问题描述】:

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

(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 次绑定。代码确实三个绑定。”所以我不确定。

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

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

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

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

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

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

【问题讨论】:

  • 请注意,这里的答案特别关注 Common Lisp,因为您很可能会问到它,但也有许多其他具有不同范围规则的 Lisp 方言。例如。 Clojure 既有 - 词法绑定,也有动态绑定,还区分了 Symbols 和 Vars。所以不要向所有 Lisps传播任何规则。
  • @ffriend:Common Lisp 也有词法和动态绑定。拉斯的回答中引用的定义对两者都是正确的。不过,总的来说,您是对的:“变量”和“绑定”这两个术语的定义细节可能在不同的语言之间有很大差异。

标签: variables lisp


【解决方案1】:

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

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

所以答案是:

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

【讨论】:

  • 在 C 上,它是一个变量吗?或者这是事情变得不那么稳定的地方?
  • 不,每个函数调用都会创建一个新的绑定,根据 CL 术语,这也是一个新变量。
【解决方案2】:

我认为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)

嵌套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-firstget-second 返回相同的值,set-firstset-second 都更新该值。闭包关闭 same 绑定。

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

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

(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 上的这些线程:

【讨论】:

  • 说到实用的 Common Lisp 链接,该链接说“一个变量甚至可以同时具有多个绑定;递归函数的参数”。这是否意味着 C 的答案是“一个名称、一个变量、多个绑定”?但是另一个答案中的 CLHS 术语链接说变量是一个绑定,这似乎表明 C 的答案是“一个名称,多个变量,多个绑定”。或者这是否意味着尝试计算变量的数量可能不是一个好主意?
  • 完整的引用是“一个变量——你可以在程序的源代码中指向的东西——在程序运行期间可以有许多不同的绑定。一个变量甚至可以有多个同时绑定;例如,递归函数的参数会在每次调用函数时重新绑定。”他提出的观点是,当您进行递归调用时,变量(源代码中的实际符号)具有内部调用的绑定,同时具有外部调用的绑定。就个人而言,我认为谈论这样的变量(作为符号
  • 在源代码中)并不是特别有用,HyperSpec 也没有在词汇表中使用该约定。考虑名称的绑定会更有效率(在这种情况下,Seibel 使用单词变量,而 HyperSpec 使用名称)。我想这本书并不完美,但相当不错,我希望内容总体上是有用的。
猜你喜欢
  • 2012-01-30
  • 1970-01-01
  • 2013-07-14
  • 1970-01-01
  • 2012-06-19
  • 2021-08-22
  • 1970-01-01
  • 2018-08-24
  • 1970-01-01
相关资源
最近更新 更多