Re: ONCE-ONLY
- From: Pascal Bourguignon <pjb@xxxxxxxxxxxxxxxxx>
- Date: Fri, 29 Apr 2005 17:27:35 +0200
"Vladimir Zolotykh" <gsmith@xxxxxxxxxxxxx> writes:
> Hi,
>
> Let me ask you a question about ONCE-ONLY macro, which I've read in
> Practical Common Lisp by Peter Seibel, chapter 8. Macros: Defining
> Your Own. Do you think there is a way to explain how it works? The
> idea is understandable (I would appreciate if you corrected me): (1)
> macro can't be used standalone , only inside another macro, (2) its
> purpose is to evaluate each form passed as argument only once and in
> the proper order, (3) bind each result to the local variable and
> inside the BODY argument of this macro (4) use this variable instead
> the original macro parameter of the surrounding macro. Even my
> explanation is clumsy, not to mention my understanding of what's going
> on. However, the implemetation of the macro is (IMO) quite obscure.
>
> Here is the macro itself
>
> (defmacro once-only ((&rest names) &body body)
> (let ((gensyms (loop repeat (length names) collect (gensym))))
> `(let (,@(loop for g in gensyms collect `(,g (gensym))))
> `(let (,,@(loop for g in gensyms for n in names
> collect ``(,,g ,,n)))
> ,(let (,@(loop for n in names for g in gensyms
> collect `(,n ,g)))
> ,@body)))))
Do understand a macro, a good way is to macroexpand-1 it:
[1]> (defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop repeat (length names) collect (gensym))))
`(let (,@(loop for g in gensyms collect `(,g (gensym))))
`(let (,,@(loop for g in gensyms for n in names
collect ``(,,g ,,n)))
,(let (,@(loop for n in names for g in gensyms
collect `(,n ,g)))
,@body)))))
ONCE-ONLY
[37]> (macroexpand-1 '(once-only (a b) (list 'list a b)))
(LET ((#:G4083 (GENSYM)) (#:G4084 (GENSYM)))
(LIST 'LET (LIST (LIST #:G4083 A) (LIST #:G4084 B))
(LET ((A #:G4083) (B #:G4084)) (LIST 'LIST A B)))) ;
T
Unfortunately the use of anonymous gensyms doesn't help reading this.
Let's correct it:
[246]> (defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for n in names collect (gensym (string n)))))
`(let (,@(loop for g in gensyms collect `(,g (gensym ))))
`(let (,,@(loop for g in gensyms for n in names
collect ``(,,g ,,n)))
,(let (,@(loop for n in names for g in gensyms
collect `(,n ,g)))
,@body)))))
ONCE-ONLY
[41]> (macroexpand-1 '(once-only (a b) (list 'list a b)))
(LET ((#:A4103 (GENSYM)) (#:B4104 (GENSYM)))
(LIST 'LET (LIST (LIST #:A4103 A) (LIST #:B4104 B))
(LET ((A #:A4103) (B #:B4104)) (LIST 'LIST A B)))) ;
T
Now we see that this macro expands to code that return a s-expression.
If you evaluate this s-expression you get what macros using once-only
get. To do that, we remove "#:", and we add templates for the
arguments A and B:
[47]> (let ((A :A-template) (b :b-template))
(LET ((A4103 (GENSYM)) (B4104 (GENSYM)))
(LIST 'LET (LIST (LIST A4103 A) (LIST B4104 B))
(LET ((A A4103) (B B4104)) (LIST 'LIST A B)))))
(LET ((#:G4109 :A-TEMPLATE) (#:G4110 :B-TEMPLATE)) (LIST #:G4109 #:G4110))
If you need this s-expression, you can use it in a function too.
[38]> (let ((a '(+ 1 2)) (b '(* 3 4))) (once-only (a b) (list 'list a b)))
(LET ((#:G4087 (+ 1 2)) (#:G4088 (* 3 4))) (LIST #:G4087 #:G4088))
Macros ARE NOT restricted to other macros. (Your point (1) is false).
THIS macro is rather designed to be used by other macros, but you can still
use it stand alone. It's nice to be able to write code that generate code.
[39]> (list '/ 'pi 2)
(/ PI 2)
Ok, note how the code generated by the code generated by once-only
don't contain the names of the variables given to once-only. That's
because what we're interested here in is the values of these variables
whose names were given to once-only. These values are the expressions
bound to the local variables.
Once-only made a trick, because it masked these variable holding
forms, by binding them to the name of the anonymous variable that will
hold the values of these forms, so the body could use the values
instead of the forms.
(LET ((#:G4083 (GENSYM)) (#:G4084 (GENSYM))) ; generate names
(LIST 'LET (LIST (LIST #:G4083 A) (LIST #:G4084 B)) ; generate code to evaluate
; the form and bind to
; variables named above
(LET ((A #:G4083) (B #:G4084)) (LIST 'LIST A B)))) ; and rebind the variables
; holding the forms to the name of the variable generated.
> and its simple usage
>
> (defmacro do-primes ((var start end) &body body)
> (once-only (start end)
> `(do ((,var (next-prime ,start)
> (next-prime (1+ ,var))))
> ((> ,var ,end))
> ,@body)))
>
> What I wanted to say in (4) is that: inside DO-PRIMES we have END as
> parameter, END as argument to ONCE-ONLY, and END inside the BODY
> argument to ONCE-ONLY. First two as far as I can judge is the same but
> the third is quite different, the last idea proved to be the most
> difficult to comprehend.
Well, yes. The third is a new lexical binding. Same name, new
variables. But there's also a difference between the first and the
second: the first END is a binding in the macro do-primes. Eg, it
names a variable (parameter) of do-primes. But the second is just a
value in the NAMES list. NAMES is like the first END, for the
once-only. But now, in once-only END is data.
The non evaluation of macro arguments transforms program into data.
> Would you mind helpting me here?
Try this rather:
[27]> (let ((n 1e9))
(let ((a '(ext:! n)) (b '(incf n)))
(once-only (a b) (list 'list a b a b a b)))
The expression a will be long to evaluate. And since we want to
repeat its value several time in the resulting list, we sure don't
want to evaluate it twice... The expression b changes the value of n,
which is used in the expression a. Since the programmer wrote the
expression b once, we don't want to evaluate it twice or more, only
once (otherwise it'd be a loop construct). Moreover we want to
evaluate them in the order given by the programmer, otherwise we would
return (ext:! (1+ n)) instead of (ext:! n) as the programmer
wrote. Hence the introduction of the anonymous local variables
generated by the code generated by once-only:
(LET ((#:G4049 (EXT:! 1.0E9)) (#:G4050 (+ 1 1)))
(LIST #:G4049 #:G4050 #:G4049 #:G4050 #:G4049 #:G4050))
So your point (2) is not correctly formulated. The purpose is to
evaluate the forms stored in the variables whose names are given.
(3) and (4) are correct.
--
__Pascal Bourguignon__ http://www.informatimago.com/
Wanna go outside.
Oh, no! Help! I got outside!
Let me back inside!
.
- Follow-Ups:
- Re: ONCE-ONLY
- From: Vladimir Zolotykh
- Re: ONCE-ONLY
- References:
- ONCE-ONLY
- From: Vladimir Zolotykh
- ONCE-ONLY
- Prev by Date: Re: Comparing politics to Java Exceptions
- Next by Date: Re: Comparing copyright law to Java Exceptions
- Previous by thread: ONCE-ONLY
- Next by thread: Re: ONCE-ONLY
- Index(es):
Relevant Pages
|