|
|||||
|
|
#1 |
|
|
that this macro is used to display symbols created by GENSYM, and that lead me to wonder why one needs to use GENSYM at all when writing macros. Why not something like (defmacro fiddle (widget) (let ((var #:var)) `(let ((widget* ,widget ....etc etc etc instead of "(let ((var (gensym)))"? The former certainly yields more readable macroexpansions (the main benefit, I suppose), and it isn't any worse to type. It isn't any less friendly to a WITH-GENSYMS macro. (defmacro with-gensyms (vars &body body) `(let ,(mapcar (lambda (var) `(,var ,(make-symbol (symbol-name var)))) vars) ,@body)) So is there a reason not to do this? Chris Capel |
|
|
#2 |
|
|
> I was browsing the hyperspec and came across the #: reader macro. I noticed > that this macro is used to display symbols created by GENSYM, and that lead > me to wonder why one needs to use GENSYM at all when writing macros. Why > not something like > > (defmacro fiddle (widget) > (let ((var #:var)) > `(let ((widget* ,widget > ...etc etc etc > > instead of "(let ((var (gensym)))"? The former certainly yields more > readable macroexpansions (the main benefit, I suppose), and it isn't any > worse to type. It isn't any less friendly to a WITH-GENSYMS macro. Because that gives you the same gensym-ed symbol each time you use the macro, and if you have nested occurences of the macro you might get name collisions. Paul |
|
|
#3 |
|
|
> Chris Capel wrote: >> I was browsing the hyperspec and came across the #: reader macro. I noticed >> that this macro is used to display symbols created by GENSYM, and that lead >> me to wonder why one needs to use GENSYM at all when writing macros. Why >> not something like >> (defmacro fiddle (widget) >> (let ((var #:var)) This is missing a quote, should have been (VAR '#:VAR). >> `(let ((widget* ,widget >> ...etc etc etc >> instead of "(let ((var (gensym)))"? The former certainly yields more >> readable macroexpansions (the main benefit, I suppose), and it isn't any >> worse to type. It isn't any less friendly to a WITH-GENSYMS macro. > > Because that gives you the same gensym-ed symbol each time you use > the macro, and if you have nested occurences of the macro you might > get name collisions. Besides (though less importantly), the different values from GENSYM's counter help figure out which occurrence of the symbol comes from which macro invocation. With an argument to GENSYM, e.g. (LET ((VAR (GENSYM "VAR-"))) ...), readability is preserved. ---V***il. -- V***il Nikolov <vnikolov@poboxes.com> Hollerith's Law of Docstrings: Everything can be summarized in 72 bytes. |
|
|
#4 |
|
|
Hi Chris Capel,
> The former certainly yields more readable macroexpansions This is the WITH-GENSYMS macro I use (and wrote): (defmacro with-gensyms ((&rest symbols) &body code) `(let (,@(loop for sym in symbols collect `(,sym (gensym ,(symbol-name sym))))) ,@code)) It supplies the optional string argument to GENSYM based upon the symbol name you are using for substitution. Generated symbols become just as intelligible as regular symbols with zero extra work. Regards, Adam |
|
|
#5 |
|
|
Paul wrote:
> Chris Capel wrote: > > I was browsing the hyperspec and came across the #: reader macro. I noticed > > that this macro is used to display symbols created by GENSYM, and that lead > > me to wonder why one needs to use GENSYM at all when writing macros. Why > > not something like > > > > (defmacro fiddle (widget) > > (let ((var #:var)) > > `(let ((widget* ,widget > > ...etc etc etc > > > > instead of "(let ((var (gensym)))"? The former certainly yields more > > readable macroexpansions (the main benefit, I suppose), and it isn't any > > worse to type. It isn't any less friendly to a WITH-GENSYMS macro. > > Because that gives you the same gensym-ed symbol each time you use > the macro, and if you have nested occurences of the macro you might > get name collisions. From the hyperspec (my emphasis) 2.4.8.5 Sharpsign Colon #: introduces an uninterned symbol whose name is symbol-name. Every time this syntax is encountered, a DISTINCT uninterned symbol is created. so (eq '#:a '#:a) => NIL preventing name collisions. The drawback that I can see is the duplication: (let ((long-and-descriptive-name #:long-and-descriptive-name)) I've been playing with a replacement for with-gensyms (defmacro with-syms((&rest symbol-list)&body code) `(let ,(mapcar (lambda(sym) `(,sym (copy-symbol ',sym))) symbol-list) ,@code)) For example (defmacro repeat (count &body code) (with-syms (local-index-variable fixed-upper-limit) `(do ((,local-index-variable 0 (+ ,local-index-variable 1)) (,fixed-upper-limit ,count)) ((= ,local-index-variable ,fixed-upper-limit)) ,@code))) * (repeat 2 (print 'even)(print 'odd)) EVEN ODD EVEN ODD NIL * (macroexpand-1 '(repeat (random 4) (print 'even)(print 'odd))) (DO ((#:LOCAL-INDEX-VARIABLE 0 (+ #:LOCAL-INDEX-VARIABLE 1)) (#:FIXED-UPPER-LIMIT (RANDOM 4))) ((= #:LOCAL-INDEX-VARIABLE #:FIXED-UPPER-LIMIT)) (PRINT 'EVEN) (PRINT 'ODD)) T Notice that one loses the index numbers that GENSYM gives. This looks like a win with macroexpand-1 because you are only expanding one macro anyway. What happens with macroexpand and macroexpand-all? Without the index numbers one might not be able to debug nested macros. I lack the experience to know if the loss here outweighs the gain from avoiding the clutter of unwanted numbers when using macroexpand-1. Alan Crowe Edinburgh Scotland |
|
|
#6 |
|
|
Alan Crowe <alan@cawtech.freeserve.co.uk> writes:
> so (eq '#:a '#:a) => NIL preventing name collisions. You missed the important fact of this discussion: (defun a () '#:a) (eq (a) (a)) => T !!! Agreed, for a lexical binding it would make a difference, but if you use that symbol for something else, you would have colisions as soon as you use the macro twice, and the more probably lethally so if it can be used recursively: (m (:attr 1) (m (:attr 2) (do-something)))) Should I spell a example for m? -- __Pascal Bourguignon__ http://www.informatimago.com/ Our enemies are innovative and resourceful, and so are we. They never stop thinking about new ways to harm our country and our people, and neither do we. |
|
|
#7 |
|
|
Adam Warner wrote:
> Hi Chris Capel, > >> The former certainly yields more readable macroexpansions > > This is the WITH-GENSYMS macro I use (and wrote): > > (defmacro with-gensyms ((&rest symbols) &body code) > `(let (,@(loop for sym in symbols > collect `(,sym (gensym ,(symbol-name sym))))) > ,@code)) > > It supplies the optional string argument to GENSYM based upon the symbol > name you are using for substitution. Generated symbols become just as > intelligible as regular symbols with zero extra work. Thanks! I already knew about the argument to GENSYM. The only reason to use the reader macro (if it worked) would be that it's easier to type. And easier to read too. I really do think that #:MYVAR is easier to read in a macroexpansion than #:MYVAR8435, don't you? Ohhh, I have an idea! (let ((counter 0)) (defmacro with-gensyms (vars &body body) `(let ,(mapcar (lambda (var) `(,var (make-symbol ,(format nil "_~A_~A" (string-downcase (symbol-name var)) (incf counter))))) vars) ,@body))) !! That's gives much more readable macroexpansions! Now instead of #:MYVAR8435, we get #:_myvar_15. Now that's an improvement. Chris Capel |
|
|
#8 |
|
|
Chris Capel <ch.ris@iba.nktech.net> writes:
> [...] > (let ((counter 0)) > (defmacro with-gensyms (vars &body body) > `(let ,(mapcar (lambda (var) > `(,var (make-symbol > ,(format nil "_~A_~A" > (string-downcase (symbol-name var)) > (incf counter))))) > vars) > ,@body))) A non-top-level DEFMACRO form is usually undesirable. (For example, the macro definition won't be available in the rest of the file.) Also, don't you want to bind *GENSYM-COUNTER* instead, so as not to duplicate what GENSYM can do? > !! That's gives much more readable macroexpansions! Now instead of > #:MYVAR8435, we get #:_myvar_15. Now that's an improvement. By the way, that would typically be printed as #:|_myvar_15| (so one may consider changing *PRINT-CASE*, rather than downcasing symbol names). Also, I am not sure that a leading underscore is good for readability. ---V***il. -- V***il Nikolov <vnikolov@poboxes.com> Hollerith's Law of Docstrings: Everything can be summarized in 72 bytes. |
|
|
#9 |
|
|
Pascal Bourguignon wrote:
> Alan Crowe <alan@cawtech.freeserve.co.uk> writes: > > so (eq '#:a '#:a) => NIL preventing name collisions. > > You missed the important fact of this discussion: > > (defun a () '#:a) > (eq (a) (a)) => T !!! Oh no! I got my "times when things happen" muddled. #:a generates distinct symbols at read time. I would have to re-read my (defmacro ... ) from the source file to get a distinct symbol, which isn't any use at all for writing macros. (defun a () '#:a) (eq (a) (progn (eval '(defun a () '#:a)) (a))) => NIL This is much more subtle than I realised: (defmacro repeat (count &body code) ;; code that skates on thin ice, ;; alone, in the dark. (let ((i '#:i) (n '#:n)) `(do ((,i 0 (+ ,i 1)) (,n ,count)) ((= ,i ,n)) ,@code))) (repeat 2 (repeat 2 (write '*))) => **** NIL I avoid capturing any variables from programmer written code, due to using #: (the bit I understood) I avoid the nested calls of repeat standing on each others toes. I incorrectly thought I was getting distinct symbols, because I missed the point that read time is all over before macroexpansion time comes. But I get lucky and lexical scoping saves me with distinct, nested bindings of the same symbol. > Should I spell a example for m? Yes please. I've only ever written macros in which I would be rescued by lexical scoping. Something fancier would expand my horizons. Alan Crowe Edinburgh Scotland |
|
|
#10 |
|
|
Alan Crowe <alan@cawtech.freeserve.co.uk> writes:
> Pascal Bourguignon wrote: > > Alan Crowe <alan@cawtech.freeserve.co.uk> writes: > > > so (eq '#:a '#:a) => NIL preventing name collisions. > > > > You missed the important fact of this discussion: > > > > (defun a () '#:a) > > (eq (a) (a)) => T !!! > > Oh no! I got my "times when things happen" muddled. > #:a generates distinct symbols at read time. I would have to > re-read my (defmacro ... ) from the source file to get a > distinct symbol, which isn't any use at all for writing > macros. > > (defun a () '#:a) > (eq (a) (progn (eval '(defun a () '#:a)) (a))) => NIL > > This is much more subtle than I realised: > > (defmacro repeat (count &body code) > ;; code that skates on thin ice, > ;; alone, in the dark. > (let ((i '#:i) > (n '#:n)) > `(do ((,i 0 (+ ,i 1)) > (,n ,count)) > ((= ,i ,n)) > ,@code))) > > (repeat 2 (repeat 2 (write '*))) > => **** > NIL > > I avoid capturing any variables from programmer written > code, due to using #: (the bit I understood) > > I avoid the nested calls of repeat standing on each others > toes. I incorrectly thought I was getting distinct symbols, > because I missed the point that read time is all over before > macroexpansion time comes. But I get lucky and lexical > scoping saves me with distinct, nested bindings of the same > symbol. > > > Should I spell a example for m? > > Yes please. I've only ever written macros in which I would > be rescued by lexical scoping. Something fancier would > expand my horizons. Now you should see when you can get problems: when you use the anonymous symbol for something else than a lexical variable. Anything that modifies a global state, be it the current package or for example a hash table where you store the anonymous symbol with some other data, for reference by other code generated by your macro. For example: (defmacro defcommand (pattern &body body) (let ((name #:command)) `(progn (defun ,name (args) ,@body) (push (cons ',pattern ',name) *commands*)))) (defmacro parse-command (command) `(match-case ,command ,@(mapcar (lambda (p-f) `(,(car p-f) (,(cdr-pf) ,(collect-vars ,(car p-f))))) *commands*))) (defcommand (take (?x object)) (detach object) (attach object (bag *player*))) (defcommand (throw (?x object)) (cond ((containp (bag player) object) (detach object) (attach object *ground*)) (t (error "You don't hold ~A" object)))) (loop for command = (read) do (parse-command command)) Both invocations of defcommand would define the same function, (ie. all but the first would redefine the function), and only the first command could be run. It would work if you replaced #:command by (gensym "COMMAND"). -- __Pascal Bourguignon__ http://www.informatimago.com/ Our enemies are innovative and resourceful, and so are we. They never stop thinking about new ways to harm our country and our people, and neither do we. |