在 Elisp 中,如何访问从闭包本地绑定的符号的值单元?

In Elisp, how to access the value cell of a symbol that's bound locally from a closure?

在下面的代码中,我定义了一个函数来创建一个接受一个参数的闭包,该参数的值应该是一个符号,指向一个绑定在这个闭包上下文中的变量。在闭包的主体中,我使用 symbol-value 来获取符号的值,但它提示错误 Symbol's value as variable is void,我希望评估此代码段以显示 123.

所以这里我有两个问题:

  1. 为什么 symbol-value 不起作用?
  2. 如何更正此代码段以获得所需的结果?
(defun make-closure ()
  (lexical-let ((var1 123))
    (lambda (name)
      ;; How can I get the value cell of the symbol
      ;; specified by the argument "name" ?
      ;; This doesn't work.
      (message-box (format "%s" (symbol-value name))))))

(let ((closure (make-closure)))
  (funcall closure 'var1))

已更新:

实际上,我在编写一些玩具代码时遇到了这个问题 模仿"Object Oriented"。一开始是这样的( 类似于Stefan回答的第二个代码):

(defun new-person (initial-name)
  (lexical-let* ((name initial-name)
                 (say-hi (lambda ()
                           (message-box (format "Hi, I'm %s" name))))
                 (change-name (lambda (new-name)
                                (setq name new-name))))
    (lambda (selector &rest args)
      (cond
       ((equal 'say-hi selector) (apply say-hi args))
       ((equal 'change-name selector) (apply change-name args))
       (t (message-box "Message not understood"))))))

(let ((tony (new-person "Tony")))
  (funcall tony 'say-hi)
  (funcall tony 'change-name "John")
  (funcall tony 'say-hi))

但我觉得 "cond" 的条款有点 "boilerplate" 我认为 可能可以使用从参数传递的符号,所以我 将其修改为以下内容,不再有效,但我做不到 找出原因:

(defun new-person (initial-name)
  (lexical-let* ((name initial-name)
                 (say-hi (lambda ()
                           (message-box (format "Hi, I'm %s" name))))
                 (change-name (lambda (new-name)
                                (setq name new-name))))
    (lambda (selector &rest args)
      (apply (symbol-value selector) args))))

也就是说我们不应该使用一个符号来引用一个 像上面这样的闭包中的词法绑定变量,因为名称 他们在评估中不能保证与他们一样 写在源代码中?

你在这里误解了一些事情,但关键是你用 (funcall closure 'var1) 传递的 var1 符号是 而不是 定义 lambda 函数的词法环境中的符号。

对 lexical-let 形式进行宏扩展将有助于澄清。这个:

(lexical-let ((var1 123))
  (lambda (name)
    (message-box (format "%s" (symbol-value name)))))

沿着这些线展开:

(progn
  (defvar --cl-var1--)
  (let ((--cl-var1-- 123))
    #'(lambda (name)
        (message-box (format "%s" (symbol-value name))))))

也就是说 lexical-let 重写 您在绑定中以不冲突的方式指定的符号名称。

请注意,您实际上还没有完成任何与 var1 绑定相关的事情。如果您这样做了,我们会在代码中看到对 --cl-var1-- 的额外引用。

当您将符号 var1 传递给此函数时,您传递的是 canonical var1,而不是 --cl-var1--(或任何它最终在实践中)。

这就是应该的。词法绑定的本质是它影响在该范围内编写的代码,不会影响外部代码。 (let ((closure (make-closure))) (funcall closure 'var1)) 形式在外面,因此根本看不到词法绑定的 var1


当涉及到 "correcting" 代码时,我相当难以弄清楚你想用它去哪里,但根据我的解释,你根本不需要闭包,因为你'正在寻找动态绑定而不是词汇绑定。例如:

(defun make-func ()
  (lambda (name)
    (message-box (format "%s" (symbol-value name)))))

(let ((func (make-func))
      (var1 123))
  (funcall func 'var1))

根据对问题的编辑,我建议稍微修改代码,这样您就不会对要与函数参数匹配的值使用词法绑定。例如:

(defun new-person (initial-name)
  (lexical-let*
      ((name initial-name)
       (map (list
             (cons 'say-hi (lambda ()
                             (message-box
                              (format "Hi, I'm %s" name))))
             (cons 'change-name (lambda (new-name)
                                  (setq name new-name))))))
    (lambda (selector &rest args)
      (apply (cdr (assq selector map)) args))))

词法绑定变量基本上没有名称(即它们的名称只是源代码中存在的临时工件,但在评估期间不存在)。

您可以改用 引用 变量:

;; -*- lexical-binding:t -*-

(defun make-closure ()
  (lambda (ref)
    ;; How can I get the value cell of the symbol
    ;; specified by the argument "name" ?
    ;; This doesn't work.
    (message-box (format "%s" (gv-deref ref)))))

(let ((closure (make-closure)))
  (let ((var1 123))
    (funcall closure (gv-ref var1))))

但请注意,我必须移动 var1 的 let 绑定,否则我无法从外部获取对它的引用。

另一个选项是您手动为词法变量命名:

(defun make-closure ()
  (lexical-let ((var1 123))
    (lambda (name)
      ;; How can I get the value cell of the symbol
      ;; specified by the argument "name" ?
      ;; This doesn't work.
      (message-box (format "%s" (pcase name
                                 ('var1 var1)
                                 (_ (error "Unknown var name %S" name))))))))

(let ((closure (make-closure)))
  (funcall closure 'var1))

请注意,我将 var1 用于两个不同的目的:一次是词法变量的名称,另一次它只是用于 select 使用哪个变量和 pcase thingy 将一个翻译成另一个:我们可以使用 "any" 其他名称作为词法绑定的 var,代码也同样有效(无需更改外部调用者)。