Common Lisp (SBCL) 中的动态变量闭包

Dynamic Variable Closure in Common Lisp (SBCL)

我了解这段代码的工作原理:

(defvar *nums* '(2 3 5))

(defun print-nums ()
  (format t "~a~%" *nums*))

(print-nums)
-> (2 3 5)
-> NIL

我什至明白动态绑定变量 *nums* 的新值是如何在这段代码中传递给 print-nums 的:

(let ((*nums* '(1 2 3)))
  (print-nums))
-> (1 2 3)
-> NIL

但为什么下面的代码不以同样的方式工作?

(defvar *my-nums-f* (let ((*nums* '(1 2 3)))
                      #'(lambda () (format t "~a~%" *nums*))))

(funcall *my-nums-f*)
-> (2 3 5)
-> NIL

闭包的概念不适用于动态绑定变量,还是我做错了什么?如果我错误地理解了闭包的概念,谁能给我解释一下?

(defvar *my-nums-f* (let ((*nums* '(1 2 3)))
                      #'(lambda () (format t "~a~%" *nums*))))

这里我们设置*my-nums-f*

的结果
(let ((*nums* '(1 2 3)))
  #'(lambda () (format t "~a~%" *nums*)))

这个表单首先动态绑定*nums*'(1 2 3),然后返回#'(lambda () (format t "~a~%" *nums*)),这是一个函数。最后,我们将 *nums* 重置为 '(2 3 5)

(funcall *my-nums-f*)

这里我们调用存储在*my-nums-f*中的函数,即(lambda () (format t "~a~%" *nums*))。此函数取 *nums* 的当前值,即 '(2 3 5).

-> (2 3 5)
-> NIL

您似乎期望 lambda 以某种方式内联函数主体中使用的所有变量的当前值,这确实会在该点导致 (format t "~a~%" '(1 2 3))

但这不是它的工作原理:函数引用变量本身,而不是变量值的快照。这就是为什么该函数在调用时会看到 *nums* 的当前值。

这是模拟动态变量的另一种外观。假设 NUMS 是在调用 BAR 之前绑定到 '(2 3 5) 的动态变量。 NUMS 在 lambda 中是免费的,并且是封闭的。但是,在 lambda returns 之前,NUMS 是反弹。这就是当动态 var 超出将新绑定推送到它的小值堆栈的词法范围时发生的情况。

(defun bar ()
  (let ((nums '(1 2 3)))
    (prog1
        (lambda () nums)
      (setf nums '(2 3 5)))))

SBCL 会告诉您它是否创建了闭包:#<FUNCTION ...>#<CLOSURE ...>。您的代码不会创建闭包,因为 lambda 主体中没有自由变量。