使用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,因为符号xcbr的参数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)