> Programming Languages > Lisp
Various Topics Home | Disclaimer | Report Adult Posts

Various Topics on Lisp



Lisp - "Sharp-colon reader macro" in Programming Languages


Old 09-19-2004   #1
..r.. ..p..
 
Default Sharp-colon reader macro

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
 
Old 09-19-2004   #2
.... .. ..e..
 
Default Re: Sharp-colon reader macro

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
 
Old 09-19-2004   #3
..ss.. ..kol..
 
Default Re: Sharp-colon reader macro

"Paul F. Dietz" <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.


---V***il.


--
V***il Nikolov <vnikolov@poboxes.com>

Hollerith's Law of Docstrings: Everything can be summarized in 72 bytes.
 
Old 09-19-2004   #4
.... ..rn..
 
Default Re: Sharp-colon reader macro

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
 
Old 09-19-2004   #5
.... ..o..
 
Default Re: Sharp-colon reader macro

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
 
Old 09-19-2004   #6
..sc.. ..urguign..
 
Default Re: Sharp-colon reader macro

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)
(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.
 
Old 09-19-2004   #7
..r.. ..p..
 
Default Re: Sharp-colon reader macro

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
 
Old 09-20-2004   #8
..ss.. ..kol..
 
Default Re: Sharp-colon reader macro

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.
 
Old 09-20-2004   #9
.... ..o..
 
Default Re: Sharp-colon reader macro

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
 
Old 09-20-2004   #10
..sc.. ..urguign..
 
Default Re: Sharp-colon reader macro

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.
 

Thread Tools
Display Modes





Powered by vBulletin®
Copyright ©2000 - 2009, Jelsoft Enterprises Ltd.
Search Engine Friendly URLs by vBSEO 3.3.0