Subject: Re: lambda-returning defmacro, capture
From: rpw3@rpw3.org (Rob Warnock)
Date: Fri, 15 Dec 2006 01:35:11 -0600
Newsgroups: comp.lang.lisp
Message-ID: <UNOdnYQFX9CyzR_YnZ2dnUVZ_ompnZ2d@speakeasy.net>
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