The expansion of a macro often needs to use symbols that weren't passed as arguments by the user (as names for local variables, for example). One must make sure that such symbols cannot conflict with a symbol that the user is using in the surrounding code.
This is usually achieved by using GENSYM
, a function that returns a fresh uninterned symbol.
Bad
Consider the macro below. It makes a DOTIMES
-loop that also collects the result of the body into a list, which is returned at the end.
(defmacro dotimes+collect ((var count) &body body)
`(let ((result (list)))
(dotimes (,var ,count (nreverse result))
(push (progn ,@body) result))))
(dotimes+collect (i 5)
(format t "~a~%" i)
(* i i))
; 0
; 1
; 2
; 3
; 4
;=> (0 1 4 9 16)
This seems to work in this case, but if the user happened to have a variable name RESULT
, which they use in the body, the results would probably not be what the user expects. Consider this attempt to write a function that collects a list of sums of all integers up to N
:
(defun sums-upto (n)
(let ((result 0))
(dotimes+collect (i n)
(incf result i))))
(sums-upto 10) ;=> Error!
Good
To fix the problem, we need to use GENSYM
to generate a unique name for the RESULT
-variable in the macro expansion.
(defmacro dotimes+collect ((var count) &body body)
(let ((result-symbol (gensym "RESULT")))
`(let ((,result-symbol (list)))
(dotimes (,var ,count (nreverse ,result-symbol))
(push (progn ,@body) ,result-symbol)))))
(sums-upto 10) ;=> (0 1 3 6 10 15 21 28 36 45)
TODO: How to make symbols from strings
TODO: Avoiding problems with symbols in different packages