Oops! Yesterday I wrote this:
+---------------
| [Ise] DEFINE-SYMBOL-MACRO (usually inside a macro called DEFLEXICAL
| or DEFLEX) to define "global lexical variables" which can
| then be shadowed by lexical bindings. Here's the simplest
| version of it I've seen so far:
|
| > (defmacro deflex (var val &optional (doc nil docp))
| (let ((backing-var (make-symbol (symbol-name var))))
| `(progn
| (defparameter ,backing-var ,val ,doc)
| ,@(when docp `((setf (documentation ',var 'variable) ,doc)))
| (define-symbol-macro ,var ,backing-var))))
|
| DEFLEX
| > (deflex foo 13)
|
| FOO
| > (defun foo () foo)
|
| FOO
| > (let ((foo 234))
| (list foo (foo)))
|
| (234 13)
| >
+---------------
Turns out that version has a *serious* bug!! I was reading some
old saved articles on this topic, and saw a comment by Tim Bradshaw
noting that a second DEFLEX will generate an expansion to a *new*
uninterned symbol, and that doesn't do what I expect/want at *all*.
Continuing the above example will show the bug:
> (foo)
13
> (deflex foo 54)
FOO
> foo
54
> (foo)
13
>
I confess that when I said that the above was "the simplest version
of it I've seen so far", I did *not* mean that that's the version I
myself have been using, which is quite a bit uglier [see "p.s." below].
I thought the above was cute and short, but clearly didn't think it
through far enough. Mea culpa.
Here's an equally-short one based on what I've seen in this recent
thread. I *don't* know that it works reliably, either, but it passes
a few quick tests:
> (defmacro deflex (var val &optional (doc nil docp))
`(progn
(locally (declare (special ,var))
(setf (symbol-value ',var) ,val))
,@(when docp `((setf (documentation ',var 'variable) ,doc)))
(define-symbol-macro ,var (symbol-value ',var))))
DEFLEX
> (deflex foo 13)
FOO
> (defun foo () foo)
FOO
> (let ((foo 234))
(list foo (foo)))
(234 13)
> (setf foo 24)
24
> (let ((foo 234))
(list foo (foo)))
(234 24)
> (deflex foo 57)
FOO
> (let ((foo 234))
(list foo (foo)))
(234 57)
>
That's better!! ;-}
[Caveat: This version blows up in an ancient CLISP (ver. 2.29)
with a stack overflow, but seems to work o.k. in CMUCL.]
-Rob
p.s. And just in case the *second* version has some hidden flaw,
here's the really ugly version that I've actually been using myself
for years:
(defmacro deflex (var val &optional (doc nil docp))
(let ((backing-var (intern (concatenate 'string
(symbol-name '#:*deflex-var-)
(symbol-name var)
(symbol-name '#:*))
(symbol-package var)))) ; <== Important!
`(progn
(defparameter ,backing-var ,val ,doc)
,@(when docp `((setf (documentation ',var 'variable) ,doc)))
(define-symbol-macro ,var ,backing-var))))
p.p.s. Note that there's a long-standing problem in CMUCL with
DEFINE-SYMBOL-MACRO expansion, that fails no matter which of
the above you use. If FOO is a symbol macro that has been shadowed
by a local (LET ((FOO ...) ...), then (SETF FOO xyz) works fine
[that is, changes the innermost FOO] but (INCF FOO) increments
the *outer* FOO! There's been a fix for that in SBCL for about
a year, and one of the CMUCL maintainers is working get something
similar into CMUCL "real soon now". But for the moment, if you're
using CMUCL be careful about SETF-expanders (other than plain SETQ/SETF)
on symbol macros.
-----
Rob Warnock <rpw3@rpw3.org>
627 26th Avenue <URL:http://rpw3.org/>
San Mateo, CA 94403 (650)572-2607