使用elisp symbol实现call-by-reference,但无法从`symbol-value`获取值
Using elisp symbol to implement call-by-reference, but can not get value from `symbol-value`
Emacs-lisp 默认使用 call-by-value, but I'm trying use its symbol mechanism to simulate call-by-reference。
例如,
(setq lexical-binding nil)
(defun cbr (x)
(message "cbr (symbol-name x) %s" (symbol-name x))
(message "cbr (symbol-value x) %s" (symbol-value x))
(set x 2))
(let ((a 1))
(cbr 'a)
a)
;; cbr (symbol-name x) a
;; cbr (symbol-value x) 1
;; 2
效果不错,因为let
表达式的结果是2,确实是call-by-reference行为
但是,如果我将名称从 a
更改为 x
:
(let ((x 1))
(cbr 'x)
x)
;; cbr (symbol-name x) x
;; cbr (symbol-value x) x
;; 1
现在它不再像预期的那样工作了。
为什么?
注意在cbr
.
中甚至无法得到正确的symbol-name
我想我已经知道会发生什么了。
第二个程序returns1,因为符号x
被cbr
的参数x
捕获了。当cbr
的主体被评估时,环境中有两个绑定:一个是let绑定x = 1
,另一个是由cbr
创建的x = x
应用。 (set x 2)
中的符号x
使用后一个。
这个问题的解决方法是:
(let ((gen-x (gensym)))
(set gen-x 1)
(cbr gen-x)
(symbol-value gen-x))
;; cbr (symbol-name x) g36
;; cbr (symbol-value x) 1
;; 2
从这里应该清楚的是,依赖动态范围和 symbol-value
是一场灾难:你到处都需要 gensyms。任何事情都依赖动态范围通常是一场灾难,除了在特定的、罕见但非常有用的情况下,您实际上需要动态范围。
但是解决这个问题很简单,即使在 elisp 中,具有词法范围。这是一种简单的方法:
(defmacro ref (form)
;; Construct a reference to a form
(unless lexical-binding
(error "doomed"))
(let ((<setting> (gensym)))
`(lambda (&rest ,<setting>) ;hack for &optional (v nil vp)
(cond
((null ,<setting>)
,form)
((null (cdr ,<setting>))
(setf ,form (car ,<setting>)))
(t
(error "mutant"))))))
(defun ref-value (ref)
(funcall ref))
(defun set-ref-value (ref value)
;; should be (setf ref-value), but elisp
(funcall ref value))
现在,例如,给定:
(defun outer (v)
(let ((x 1))
(princ (format "x is first %s\n" x))
(inner (ref x) v)
(princ (format "and x is now %s\n" x))
x))
(defun inner (ref v)
(princ (format " ref is first %s\n" (ref-value ref)))
(set-ref-value ref v)
(princ (format " and ref is now %s\n" (ref-value ref))))
然后
ELISP> (outer 4)
x is first 1
ref is first 1
and ref is now 4
and x is now 4
4 (#o4, #x4, ?\C-d)
Emacs-lisp 默认使用 call-by-value, but I'm trying use its symbol mechanism to simulate call-by-reference。
例如,
(setq lexical-binding nil)
(defun cbr (x)
(message "cbr (symbol-name x) %s" (symbol-name x))
(message "cbr (symbol-value x) %s" (symbol-value x))
(set x 2))
(let ((a 1))
(cbr 'a)
a)
;; cbr (symbol-name x) a
;; cbr (symbol-value x) 1
;; 2
效果不错,因为let
表达式的结果是2,确实是call-by-reference行为
但是,如果我将名称从 a
更改为 x
:
(let ((x 1))
(cbr 'x)
x)
;; cbr (symbol-name x) x
;; cbr (symbol-value x) x
;; 1
现在它不再像预期的那样工作了。
为什么?
注意在cbr
.
symbol-name
我想我已经知道会发生什么了。
第二个程序returns1,因为符号x
被cbr
的参数x
捕获了。当cbr
的主体被评估时,环境中有两个绑定:一个是let绑定x = 1
,另一个是由cbr
创建的x = x
应用。 (set x 2)
中的符号x
使用后一个。
这个问题的解决方法是:
(let ((gen-x (gensym)))
(set gen-x 1)
(cbr gen-x)
(symbol-value gen-x))
;; cbr (symbol-name x) g36
;; cbr (symbol-value x) 1
;; 2
从这里应该清楚的是,依赖动态范围和 symbol-value
是一场灾难:你到处都需要 gensyms。任何事情都依赖动态范围通常是一场灾难,除了在特定的、罕见但非常有用的情况下,您实际上需要动态范围。
但是解决这个问题很简单,即使在 elisp 中,具有词法范围。这是一种简单的方法:
(defmacro ref (form)
;; Construct a reference to a form
(unless lexical-binding
(error "doomed"))
(let ((<setting> (gensym)))
`(lambda (&rest ,<setting>) ;hack for &optional (v nil vp)
(cond
((null ,<setting>)
,form)
((null (cdr ,<setting>))
(setf ,form (car ,<setting>)))
(t
(error "mutant"))))))
(defun ref-value (ref)
(funcall ref))
(defun set-ref-value (ref value)
;; should be (setf ref-value), but elisp
(funcall ref value))
现在,例如,给定:
(defun outer (v)
(let ((x 1))
(princ (format "x is first %s\n" x))
(inner (ref x) v)
(princ (format "and x is now %s\n" x))
x))
(defun inner (ref v)
(princ (format " ref is first %s\n" (ref-value ref)))
(set-ref-value ref v)
(princ (format " and ref is now %s\n" (ref-value ref))))
然后
ELISP> (outer 4)
x is first 1
ref is first 1
and ref is now 4
and x is now 4
4 (#o4, #x4, ?\C-d)