lisp - Common Lisp 中的动态变量和词法变量

标签 lisp common-lisp lexical-scope

我正在阅读 Peter Seibel 的《Practical Common Lisp》一书。

第 6 章“变量”部分 “词法变量和闭包”和“动态变量,又名特殊变量”。 http://www.gigamonkeys.com/book/variables.html

我的问题是,这两部分中的示例都显示了 (let ...) 如何隐藏全局变量,并没有真正说明动态变量和词法变量之间的区别。

我理解闭包是如何工作的,但我真的不明白 let 在这个例子中有什么特别之处:

(defvar *x* 10)

(defun foo ()
  (format t "Before assignment~18tX: ~d~%" *x*)
  (setf *x* (+ 1 *x*))
  (format t "After assignment~18tX: ~d~%" *x*))


(defun bar ()
  (foo)
  (let ((*x* 20)) (foo))
  (foo))


CL-USER> (foo)
Before assignment X: 10
After assignment  X: 11
NIL


CL-USER> (bar)
Before assignment X: 11
After assignment  X: 12
Before assignment X: 20
After assignment  X: 21
Before assignment X: 12
After assignment  X: 13
NIL

我觉得这里没什么特别的。 bar 中的外部 foo 递增全局 xfoolet 包围在 bar 中增加阴影 x。有什么大不了的?我不明白这应该如何解释词法变量和动态变量之间的区别。然而这本书继续是这样的:

So how does this work? How does LET know that when it binds x it's supposed to create a dynamic binding rather than a normal lexical binding? It knows because the name has been declared special.12 The name of every variable defined with DEFVAR and DEFPARAMETER is automatically declared globally special.

如果 let 使用“正常词法绑定(bind)” 绑定(bind) x 会发生什么?总而言之,动态绑定(bind)和词法绑定(bind)之间有什么区别?这个示例在动态绑定(bind)方面有何特殊之处?

最佳答案

这是怎么回事?

你说:感觉这里没什么特别的。 bar 中的外部 foo 递增全局 xfoolet 包围在 bar 中增加阴影 x。有什么大不了的?

这里发生的特殊LET可以隐藏*x*的值。使用词法变量是不可能的。

代码通过 DEFVAR*x* 声明为特殊

FOO 中,现在 *x* 的值是动态查找的。 FOO 将采用 *x* 的当前动态绑定(bind),或者,如果没有,则采用符号 *x 的符号值*。例如,可以使用 LET 引入新的动态绑定(bind)

另一方面,词法变量必须存在于词法环境中的某处。 LETLAMBDADEFUN 等可以引入这样的词法变量。请参阅此处以三种不同方式引入的词法变量 x:

(let ((x 3))
  (* (sin x) (cos x)))

(lambda (x)
  (* (sin x) (cos x)))

(defun baz (x)
  (* (sin x) (cos x)))

如果我们的代码是:

(defvar x 0)

(let ((x 3))
  (* (sin x) (cos x)))

(lambda (x)
  (* (sin x) (cos x)))

(defun baz (x)
  (* (sin x) (cos x)))

然后 X 在上述所有三种情况下都是特殊,因为 DEFVAR 声明声明了 X成为特殊 - 全局所有级别。因此,存在将特殊 变量声明为*X* 的约定。因此,根据惯例,只有周围有星号的变量才是特殊。这是一个有用的约定。

然后在您的代码中:

(defun bar ()
  (foo)
  (let ((*x* 20))
    (foo))
  (foo))

由于 *x* 已通过上面的 DEFVAR 在您的代码中声明为 special,因此 LET 结构为 *x* 引入了一个新的动态绑定(bind)FOO 然后被调用。由于在 FOO 中,*x* 使用了动态绑定(bind),它会查找当前绑定(bind)并发现 *x* 动态绑定(bind)到 20

特殊变量的值在当前动态绑定(bind)中找到。

本地特殊声明

还有局部特殊声明:

(defun foo-s ()
  (declare (special *x*))
  (+ *x* 1))

如果变量已被DEFVARDEFPARAMETER 声明为special,则局部special 声明可以被省略。

词法变量直接引用变量绑定(bind):

(defun foo-l (x)
  (+ x 1))

让我们在实践中看看:

(let ((f (let ((x 10))
           (lambda ()
             (setq x (+ x 1))))))
  (print (funcall f))    ; form 1
  (let ((x 20))          ; form 2
    (print (funcall f))))

这里所有的变量都是词法的。在 form 2 中,LET 不会隐藏我们函数 f 中的 X。它不能。该函数使用词法绑定(bind)变量,由 LET ((X 10) 引入。用 form 2 中的另一个词法绑定(bind) X 包围调用对我们的功能没有影响。

让我们试试特殊变量:

(let ((f (let ((x 10))
           (declare (special x))
           (lambda ()
             (setq x (+ x 1))))))
  (print (funcall f))    ; form 1
  (let ((x 20))          ; form 2
    (declare (special x))
    (print (funcall f))))

现在怎么办?这行得通吗?

没有!

第一种形式 调用该函数并尝试查找X 的动态值,但没有找到。我们在 form 1 中得到一个错误:X 未绑定(bind),因为没有生效的动态绑定(bind)。

Form 2 可以工作,因为带有 special 声明的 LETX 引入了动态绑定(bind)。

关于lisp - Common Lisp 中的动态变量和词法变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/463463/

相关文章:

macros - 试图重写一个丑陋的宏

programming-languages - 函数式编程语言的好处和用途

Emacs/AUCTeX : Rewriting the Okular-make-url function to work with new synctex (full path + "./") syntax

scope - 普通 Lisp : check if lexical variable exists?

go - 理解 golang 中的词法作用域

lisp - 动态作用域可以实现词法作用域吗?

lisp - 从终端命令提示符运行 Common Lisp 函数

clojure - Clojure中Common Lisp的符号名称?

lisp - 为什么 (list 'quote ' x) 的计算结果为 'x and not (' x) 或 (quote 'x)?

javascript - 了解范围