宏的作用是将常见的句法模式抽象掉,而反复出现在宏编写的特定模式同样也可受益于其抽象能力。
最后的几个do-primes函数中,都是以let形式确保子形式仅被求值一次的,引入了一些变量用来保存宏展开过程中用到的生成符号。
这也是一个常见模式,同样的也可以使用一个宏来将它抽象掉。
;;;编写宏中宏
(defmacro with-gensyms ((&rest names) &body body)
`(let ,(loop for n in names collect `(,n (gensym)))
,@body))
(defmacro do-primes-4 ( (var start end) &body body)
(with-gensyms (ending-value)
`(do ((,var(next-prime ,start) (next-prime(1+ ,var)))
(,ending-value ,end))
((> ,var ,ending-value))
,@body)))
你可以通过将name替换成一个符号的列表,从而测试LOOP表达式生成的代码:
CL-USER> (loop for n in '(hello world) collect `(,n (gensym)))
((HELLO (GENSYM)) (WORLD (GENSYM)))
当编译关于do-primes的defmacro时,with-gensyms形式就被展开并被编译。
这样,do-primes的编译版本就已经跟你手写外层的let时一样。
当编译一个使用了do-primes的函数时,由with-gensyms生成的代码将会运行用来生成do-primes的展开式,
但with-gensyms宏本身在编译一个do-primes形式时并不会被用到,因为在do-primes被编译时,with-gensyms已经被展开。