Reputation: 1429
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
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
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