Common Lisp Macro – definition


chapter 8 defining your own

The Story of Mac: A Just-So Story

Macro Expansion Time vs. Runtime

macro will be used by the compiler to generate the code that will then be compiled,the time when macros run is called macro expansion time
run time when regular code

defmacro

(defmacro name (parameter*)
;;optional documentation string
body-form*)

The job of a macro is to translate a macro form—in other words, a Lisp form whose first
element is the name of the macro—into code that does a particular thing.

To sum up, the steps to writing a macro are as follows:
1. Write a sample call to the macro and the code it should expand into, or vice versa.
2. Write code that generates the handwritten expansion from the arguments in the
sample call.
3. Make sure the macro abstraction doesn’t “leak.”

(defun primep (number)
(when (> number 1)
(loop for fac from 2 to (isqrt number) never (zerop (mod number fac)))))

(defun next-prime (number)
(loop for n from number when (primep n) return n))

(defmacro do-primes ((var start end) &body body)
`(do ((,var (next-prime ,start) (next-prime (1+ ,var))))
((> ,var ,end))
,@body))

generating the expansion

Table 8-1. Backquote Examples
Backquote Syntax Equivalent List-Building Code Result
`(a (+ 1 2) c) (list ‘a ‘(+ 1 2) ‘c) (a (+ 1 2) c)
`(a ,(+ 1 2) c) (list ‘a (+ 1 2) ‘c) (a 3 c)
`(a (list 1 2) c) (list ‘a ‘(list 1 2) ‘c) (a (list 1 2) c)
`(a ,(list 1 2) c) (list ‘a (list 1 2) ‘c) (a (1 2) c)
`(a ,@(list 1 2) c) (append (list ‘a) (list 1 2) (list ‘c)) (a 1 2 c)

plugging the leaks

first leak:
(do-primes (p 0 (random 100))
(format t “~d ” p))

fixed:
(defmacro do-primes ((var start end) &body body)
`(do ((ending-value ,end)
((,var (next-prime ,start) (next-prime (1+ ,var))))
((> ,var ,end))
,@body))

but the solution cause other two leaks:
second leak:
the expression passed as end will be evaluated before the expression passed as start,opposite to the order they appear in the macro call
fixed:
(defmacro do-primes ((var start end) &body body)
`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
((ending-value ,end))
((> ,var ,end))
,@body))
third leak:
using like this
(do-primes (ending-value 0 10)
(print ending-value))
because: there is a symbol ending-value inner macro
macroexpand-1 can show you the problem,use like this: (macroexpand-1 ‘(do-primes (ending-value 0 (random 100)) (format t “~d ” p)))

fixed: gensym
(defmacro do-primes ((var start end) &body body)
(let ((ending-value-name (gensym)))
`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
(,ending-value-name ,end))
((> ,var ,ending-value-name))
,@body)))

three rules:
• Unless there’s a particular reason to do otherwise, include any subforms in the expansion in
positions that will be evaluated in the same order as the subforms appear in the macro call.
• Unless there’s a particular reason to do otherwise, make sure subforms are evaluated
only once by creating a variable in the expansion to hold the value of evaluating the
argument form and then using that variable anywhere else the value is needed in the
expansion.
• Use GENSYM at macro expansion time to create variable names used in the expansion.

Macro-Writing Macros

(defmacro do-primes ((var start end) &body body)
(with-gensyms (ending-value-name)
`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
(,ending-value-name ,end))
((>, var ,ending-value-name))
,@body)))
(defmacro with-gensyms ((&rest names) &body body)
`(let ,(loop for n in names collect `(,n (gensym)))
,@body))


发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注