chansey
chansey

Reputation: 1429

Using elisp symbol to implement call-by-reference, but can not get value from `symbol-value`

Emacs-lisp is default using call-by-value, but I'm trying use its symbol mechanism to simulate call-by-reference.

For example,

(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

It works well, because the result of let expression is 2, so it is indeed the call-by-reference behavior.

However, if I change the name from a to x:

(let ((x 1))
  (cbr 'x)
  x)
;; cbr (symbol-name x) x
;; cbr (symbol-value x) x
;; 1

Now it doesn't work as expected anymore.

Why?

Notice that it even can not get the correct symbol-name in cbr.

Upvotes: 1

Views: 177

Answers (2)

ignis volens
ignis volens

Reputation: 9282

What should be clear from this is that relying on dynamic scope and symbol-value is a disaster: you need gensyms all over the place. Relying on dynamic scope for anything is generally a disaster, except in the specific, rare but extremely useful, case where you actually want dynamic scope.

But solving this problem trivial, even in elisp, with lexical scope. Here is one simple approach:

(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))

And now, for instance, given:

(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))))

Then

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)

Upvotes: 1

chansey
chansey

Reputation: 1429

I think I have known what happen.

The second program returns 1, because the symbol x is captured by cbr's param x. When the body of cbr is evaluated, there are two bindings in the environment: one is the let binding x = 1, the other is x = x which is created by cbr's application. The symbol x in the (set x 2) uses the later one.

A workaround of this question is:

(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 

Upvotes: 2

Related Questions