在 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
.
所以这里我有两个问题:
- 为什么
symbol-value
不起作用?
- 如何更正此代码段以获得所需的结果?
(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,代码也同样有效(无需更改外部调用者)。
在下面的代码中,我定义了一个函数来创建一个接受一个参数的闭包,该参数的值应该是一个符号,指向一个绑定在这个闭包上下文中的变量。在闭包的主体中,我使用 symbol-value
来获取符号的值,但它提示错误 Symbol's value as variable is void
,我希望评估此代码段以显示 123
.
所以这里我有两个问题:
- 为什么
symbol-value
不起作用? - 如何更正此代码段以获得所需的结果?
(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,代码也同样有效(无需更改外部调用者)。