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.
(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
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.
Paul
"Paul F. Dietz" <·····@dls.net> writes:
> 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.
---Vassil.
--
Vassil Nikolov <········@poboxes.com>
Hollerith's Law of Docstrings: Everything can be summarized in 72 bytes.
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
Alan Crowe <····@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)
(print
(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.
Pascal Bourguignon wrote:
> Alan Crowe <····@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
Alan Crowe <····@cawtech.freeserve.co.uk> writes:
> Pascal Bourguignon wrote:
> > Alan Crowe <····@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.
Pascal Bourguignon wrote:
> 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))
Thankyou Pascal, that is a tremendous example. I'm
stunned. My own efforts to contrive a need for GENSYM rather
than Sharpsign Colon started with special variables
(excessive use of special variables is the royal road to
toxic symbol clashes, I think). Err, then I got stuck,
without a credible scenario.
I'm especially impressed because I had toyed with this kind
of adventure code quite recently. How should parse command
work? I was doing (apply (car command)(cdr command)). I
avoided clashes between my commands and functions in the
program by having a command package. The functions that
implemented the commands started
(defun command:take (&rest stuff) etc etc)
I was well pleased with this code, so to have your example
code show me, in passing, a better way has expanded my
horizons wonderfully. I've spent a few hours writing a
minimal pattern matcher and filling in the details so I can
play with running code and soak up the implications. Now I
know why GENSYM clutters up my macroexpansions with those
irritating numbers. When I look in *COMMANDS* I really want
to see:
(((HIT MONSTER WITH (X)) . #:HIT-971)
((DROP (X) ON MONSTER) . #:DROP-970)
((THROW ROCK AT MONSTER) . #:THROW-969)
((THROW (X) AT (Y)) . #:THROW-968))
with numbers to tell me that the THROW symbols are distinct
and it is all working right.
Having powerful macros to type all the boilerplate for you
makes programming fun
* (let ((*readtable* (copy-readtable)))
(set-macro-character #\newline (get-macro-character #\) ))
(loop for command = (progn
(format t "~&> ")
(read-delimited-list #\newline))
do (parse-command command)))
> hit monster with sword
The monster snarls at you.
> drop anvil on monster
You got a step ladder to stand on?
> hit monster with rock
The monster snarls at you.
> throw rock at monster
The monster picks you up by the scruff of the neck
and drops you into the debugger!
Error in function "Top-Level Form": Broken fourth wall.
Restarts:
0: [CONTINUE] Return to the dungeon
1: [ABORT ] Return to Top-Level.
Alan Crowe
Edinburgh
Scotland
Alan Crowe <····@cawtech.freeserve.co.uk> writes:
> I'm especially impressed because I had toyed with this kind
> of adventure code quite recently. How should parse command
> work? I was doing (apply (car command)(cdr command)). I
> avoided clashes between my commands and functions in the
> program by having a command package. The functions that
> implemented the commands started
A first, quick-and-dirty implementation could be to use a pattern
matched. You could try Fare's pattern matcher, or mine ( temporarily
at: http://thalassa.informatimago.com/local/pmatch.lisp ) which may be
simplier and easier to understand. It's not very efficient since each
pattern is tried sequentially. I'm going to rewrite it using a DFA,
as if a regexp. Note the match-case macro.
> I was well pleased with this code, so to have your example
> code show me, in passing, a better way has expanded my
> horizons wonderfully. I've spent a few hours writing a
> minimal pattern matcher and filling in the details so I can
> play with running code and soak up the implications. Now I
> know why GENSYM clutters up my macroexpansions with those
> irritating numbers. When I look in *COMMANDS* I really want
> to see:
>
> (((HIT MONSTER WITH (X)) . #:HIT-971)
> ((DROP (X) ON MONSTER) . #:DROP-970)
> ((THROW ROCK AT MONSTER) . #:THROW-969)
> ((THROW (X) AT (Y)) . #:THROW-968))
>
> with numbers to tell me that the THROW symbols are distinct
> and it is all working right.
>
> Having powerful macros to type all the boilerplate for you
> makes programming fun
Indeed.
> * (let ((*readtable* (copy-readtable)))
> (set-macro-character #\newline (get-macro-character #\) ))
> (loop for command = (progn
> (format t "~&> ")
> (read-delimited-list #\newline))
> do (parse-command command)))
>
> > hit monster with sword
> The monster snarls at you.
> > drop anvil on monster
> You got a step ladder to stand on?
> > hit monster with rock
> The monster snarls at you.
> > throw rock at monster
> The monster picks you up by the scruff of the neck
> and drops you into the debugger!
>
> Error in function "Top-Level Form": Broken fourth wall.
>
> Restarts:
> 0: [CONTINUE] Return to the dungeon
> 1: [ABORT ] Return to Top-Level.
:-)
--
__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.
Alan Crowe <····@cawtech.freeserve.co.uk> writes:
> [...]
> lexical scoping saves me with distinct, nested bindings of the same
> symbol.
Not always.
Consider the following (simplistic) example of providing read-only
access to the iteration counter:
(defmacro repeat ((counter-reader-name count-form) &body body)
(let ((counter-var (gensym)))
`(macrolet ((,counter-reader-name () '(identity ,counter-var)))
(dotimes (,counter-var ,count-form) ,@body))))
and the following use:
(repeat (i 2) (repeat (j 2) (format t "~%~D ~D" (i) (j))))
and then the result from replacing the call to GENSYM with
'#:COUNTER.
---Vassil.
--
Vassil Nikolov <········@poboxes.com>
Hollerith's Law of Docstrings: Everything can be summarized in 72 bytes.
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
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
Chris Capel <······@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.
---Vassil.
--
Vassil Nikolov <········@poboxes.com>
Hollerith's Law of Docstrings: Everything can be summarized in 72 bytes.