Note: The best book for Lisp macro programming is Paul
Graham's '
See http://www.cliki.net/WITH-UNIQUE-NAMES. This macro also appears in
The '
(defmacro with-unique-names (symbols &rest body) `(let ,(mapcar #'(lambda (x) `(,x (gensym))) symbols) ,@body))
The '
> (macroexpand-1 '(with-unique-names (a b c) `(let ((,a 1) (,b 2) (,c 3)) (list ,a ,b ,c)))) (let ((a (gensym)) (b (gensym)) (c (gensym))) `(let ((,a 1) (,b 2) (,c 3)) (list ,a ,b ,c)))
This translates in practice to the following idea:
(let ((a (gensym)) (b (gensym)) (c (gensym))) ; outside the expansion `(let ((gensym1 1) (gensym2 2) (gensym3 3)) ; inside the expansion (list gensym1 gensym2 gensym3)))
The variable names 'a', 'b', and 'c' have been replaced inside the macro expansion by three gensyms. This way a variable name inside the macro expansion cannot accidentally collide with a variable of the same name in the environment of the macro's expansion like shown here:
(defmacro print-macro (x) ; bad example `(let ((macro-var 'macro)) (print ,x))) > (let ((local-var 'let)) ; this works (print local-var) (print-macro local-var)) LET ; printed by PRINT LET ; printed by PRINT-MACRO > (let ((macro-var 'let)) ; this doesn't (print macro-var) (print-macro macro-var)) LET ; printed by PRINT MACRO ; printed by PRINT-MACRO
The reason for this behaviour is that the '
> (let ((local-var 'let)) ; this works (print local-var) (let ((macro-var 'macro)) (print local-var))) LET ; LOCAL-VAR inside the first LET LET ; LOCAL-VAR inside the second LET > (let ((macro-var 'let)) ; this doesn't (print macro-var) (let ((macro-var 'macro)) (print macro-var))) LET ; MACRO-VAR inside the first LET MACRO ; MACRO-VAR inside the second LET
Now the same example with unique names. Note the
comma before the
'
(defmacro print-macro (x) ; good example (with-unique-names (macro-var) `(let ((,macro-var 'macro)) (print ,x)))) > (let ((macro-var 'let)) ; now it works (print macro-var) (print-macro macro-var)) LET ; printed by PRINT LET ; printed by PRINT-MACRO
The reason why it works is that the '
> (let ((macro-var 'let)) ; works (print macro-var) (let ((gensym 'macro)) (print macro-var))) LET ; MACRO-VAR inside the first LET LET ; MACRO-VAR inside the second LET
(defmacro print-macro (x) ; good example (with-unique-names (macro-var) `(let ((,macro-var 'macro)) (print ,macro-var) (print ,x)))) > (let ((macro-var 'let)) ; works (print macro-var) (print-macro macro-var)) LET ; MACRO-VAR printed inside LET MACRO ; GENSYMed MACRO-VAR, printed inside PRINT-MACRO LET ; MACRO-VAR bound by LET, printed inside PRINT-MACRO
The expansion of the '
> (let ((macro-var 'let)) ; works (print macro-var) (let ((gensym 'macro)) (print gensym) (print macro-var))) LET ; MACRO-VAR printed inside LET MACRO ; GENSYMed MACRO-VAR printed inside PRINT-MACRO LET ; MACRO-VAR bound by LET, printed inside PRINT-MACRO
You can give as many variable names as you like to
'
(defmacro print-macro (x y z) (with-unique-names (a b c) `(let ((,a 1) (,b 2) (,c 3)) (format t "outside: a: ~a b: ~a c: ~a~%" ,x ,y ,z) (format t " inside: a: ~a b: ~a c: ~a~%" ,a ,b ,c)))) > (let ((a 'a) (b 'b) (c 'c)) (print-macro a b c)) outside: a: A b: B c: C inside: a: 1 b: 2 c: 3
Two things you still have to care about:
The 'unique names' should not use the same smbol names as the
parameter variables of the macro, otherwise you will have the same
'shadowing' effect like in ordinary Lisp functions. This is not a real
problem because when writing a macro you can see the parameter names before
your eyes, while you usually cannot see the variable names of the
environment, where the macro will be expanded.
The local gensymed variables
now themselves must be expanded by writing a
comma in front of each when they appear
inside a backquote scope.
This sometimes can lead to tricky situations, because the
comma expansion of the symbol does not
produce the variable's value, instead it produces the name of the
gensym, which holds the value.
The alternative would be writing:
(defmacro print-macro (x y z) (let ((a (gensym)) (b (gensym)) (c (gensym))) `(let ((,a 1) (,b 2) (,c 3)) (format t "outside: a: ~a b: ~a c: ~a~%" ,x ,y ,z) (format t " inside: a: ~a b: ~a c: ~a~%" ,a ,b ,c))))