27
PRACTICAL COMMON LISP Peter Seibel http://www.gigamonkeys.com/book/ 1

PRACTICAL COMMON LISP

Embed Size (px)

DESCRIPTION

PRACTICAL COMMON LISP. Peter Seibel http://www.gigamonkeys.com/book/. CHAPTER 8 MACROS: DEFINING YOUR OWN. MACRO EXPANSION TIME VS. RUNTIME. Macro expansion time : The time when macros run Runtime: the time when regular code, including the code generated by macros, runs - PowerPoint PPT Presentation

Citation preview

Page 1: PRACTICAL COMMON LISP

PRACTICAL COMMON LISP

Peter Seibelhttp://www.gigamonkeys.com/book/

1

Page 2: PRACTICAL COMMON LISP

CHAPTER 8 MACROS: DEFINING YOUR OWN

2

Page 3: PRACTICAL COMMON LISP

MACRO EXPANSION TIME VS. RUNTIME Macro expansion time: The time when macros run Runtime: the time when regular code, including the code generated by

macros, runs. When Lisp is interpreted the distinction between macro expansion time

and runtime is less clear because they're temporally intertwined (糾纏 ).

The basic skeleton of a DEFMACRO:

(defmacro name (parameter*)

"Optional documentation string."

body-form*) Like a function, a macro consists of a name, a parameter list, an

optional documentation string, and a body of Lisp expressions. However, the job of a macro isn't to do anything directly. Its job is to

translate a macro into code that does a particular thing.

3

Page 4: PRACTICAL COMMON LISP

A SAMPLE MACRO: DO-PRIMES For example:

We'll write a macro do-primes that provides a looping construct similar to DOTIMES and DOLIST except that instead of iterating over integers or elements of a list, it iterates over successive prime numbers.

First, we'll need two utility functions, one to test whether a given number is prime and another that returns the next prime number greater or equal to its argument.

;;to test whether a given number is prime

(defun primep (number)

(when (> number 1)

(loop for fac from 2 to (isqrt number) never (zerop (mod number fac)))))

;; to return the next prime number greater or equal to its argument

(defun next-prime (number)

(loop for n from number when (primep n) return n)) 4

Page 5: PRACTICAL COMMON LISP

A SAMPLE MACRO: DO-PRIMES Now we can write the macro.

Following the procedure outlined previously, we need at least one example of a call to the macro and the code into which it should expand. (do-primes (p 0 19) (format t "~d " p)) The expression expresses a loop that executes the body once each

for each prime number greater or equal to 0 and less than or equal to 19, with the variable p holding the prime number.

Without the do-primes macro, we could write such a loop with DO like this:(do ((p (next-prime 0) (next-prime (1+ p)))) ((> p 19)) (format t "~d " p))2 3 5 7 11 13 17 19NIL

5

Page 6: PRACTICAL COMMON LISP

MACRO PARAMETERS We could define do-primes with two parameters, one to hold the list and a

&rest parameter to hold the body forms, and then take apart the list by hand:(defmacro do-primes (var-and-range &rest body) (let ((var (first var-and-range)) (start (second var-and-range)) (end (third var-and-range))) `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))) ((> ,var ,end)) ,@body)))

Note that the variables var, start, and end each hold a value, extracted from var-and-range, that's then interpolated into the backquote expression that generates do-primes's expansion.(do-primes (p 0 19) (format t "~d " p)) 6

Page 7: PRACTICAL COMMON LISP

MACRO PARAMETERS However, we don‘t need to take apart (拆開 ) var-and-range “by hand”

because macro parameter lists are what are called destructuring parameter lists. Within a destructuring parameter list, a simple parameter name can be

replaced with a nested parameter list. The parameters in the nested parameter list will take their values from

the elements of the expression that would have been bound to the parameter the list replaced.

For instance, we can replace var-and-range with a list (var start end), and the three elements of the list will automatically be destructured into those three parameters.

Another special feature of macro parameter lists is that we can use &body as a synonym for &rest.

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

Page 8: PRACTICAL COMMON LISP

GENERATING THE EXPANSION Because do-primes is a simple macro, after we've destructured the

arguments, all that's left is to interpolate them into a template to get the expansion.

For simple macros like do-primes, the special backquote syntax is perfect. For instance, `(,a b) might be read as (list a 'b). Table 8-1 shows some examples of backquoted expressions along with

equivalent listbuilding code.

8

Page 9: PRACTICAL COMMON LISP

GENERATING THE EXPANSION Compare the backquoted version of do-primes to the following version,

which uses explicit list-building code, we know that backquote is a big convenience.

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

The explicit list-building code(defmacro do-primes-a ((var start end) &body body) (append '(do) (list (list (list var (list 'next-prime start) (list 'next-prime (list '1+ var))))) (list (list (list '> var end))) body))

9

Page 10: PRACTICAL COMMON LISP

Macros are defined with DEFMACRO. Its syntax is similar to DEFUN. Let’s define a simplified version of INCF to increment ordinary

variables. Our macro will take a variable name as input and construct an

expression to increment that variable by one.

(defmacro simple-incf (var) (list 'setq var (list '+ var 1)))

(setf a 4)

> (simple-incf a)5

PS. SETQ is SET Quoted. http://stackoverflow.com/questions/869529/difference-between-set-setq-and-setf-in-common-lisp

10

Page 11: PRACTICAL COMMON LISP

Now let’s modify SIMPLE-INCF to accept an optional second argument specifying the amount by which to increment the variable.

We do this with the &OPTIONAL lambda-list keyword. The default amount to increment the variable will be one.

(defmacro simple-incf (var &optional (amount 1)) (list 'setq var (list '+ var amount)))

(setf b 2)

> (simple-incf b (* 3 a))17

11

Page 12: PRACTICAL COMMON LISP

Macros do not evaluate their arguments, so the inputs to SIMPLE-INCF are the symbol B and the list (* 3 A), not the numbers 2 and 15.

12(defmacro simple-incf (var &optional (amount 1)) (list 'setq var (list '+ var amount)))

Page 13: PRACTICAL COMMON LISP

Let’s now consider why INCF has to be a macro rather than a function. Suppose we try to make an INCF function, using DEFUN.

(defun faulty-incf (var) (setq var (+ var 1)))

(setf a 7)

> (faulty-incf a)8> (faulty-incf a)8> a7

The input to FAULTY-INCF is the number 7. FAULTY-INCF creates a local variable named VAR to hold its input, and

then it increments VAR by one. It doesn’t know anything about the variable A, because its argument

was evaluated before the function was entered.

13

Page 14: PRACTICAL COMMON LISP

Write a SET-NIL macro that sets a variable to NIL.

Use backquote to write a macro called SIMPLE-ROTATEF that switches the value of two variables. For example, if A is two and B is seven, then (SIMPLEROTATEF A B)

should make A seven and B two. Obviously, setting A to B first, and then setting B to A won’t work. Your macro should expand into a LET expression that holds on to the

original values of the two variables and then assigns them their new values in its body.

14

Page 15: PRACTICAL COMMON LISP

GENERATING THE EXPANSION Test correctness of the macro in two ways.

Method 1: Run it.CL-USER> (do-primes (p 0 19) (format t "~d " p))

2 3 5 7 11 13 17 19NIL

Method 2: look at the expansion of a particular call.

CL-USER> (macroexpand-1 '(do-primes (p 0 19) (format t "~d " p)))

(DO ((P (NEXT-PRIME 0) (NEXT-PRIME (1+ P)))) ((> P 19)) (FORMAT T "~d " P))T The function MACROEXPAND-1 takes any Lisp expression as an

argument and returns the result of doing one level of macro expansion.15

Page 16: PRACTICAL COMMON LISP

PLUGGING THE LEAKS Since writing a macro is a way of creating an abstraction, we need to make

sure our macros don't leak(漏洞 ) needlessly. For example: Suppose we were to call do-primes with an expression such

as (random 100) in the end position.(do-primes (p 0 (random 100)) (format t "~d " p)) MACROEXPAND-1 shows that> (macroexpand-1 '(do-primes (p 0 (random 100)) (format t "~d " p)))(DO ((P (NEXT-PRIME 0) (NEXT-PRIME (1+ P)))) ((> P (RANDOM 100))) (FORMAT T "~d " P))T

When this expansion code is run, RANDOM will be called each time the end test for the loop is evaluated.

Thus this loop will iterate until it happens to draw a random number less than or equal to the current value of p.

While the total number of iterations will still be random, it will be drawn from a much different distribution than the uniform distribution RANDOM returns.

This is a leak in the abstraction because the caller needs to be aware that the end form is going to be evaluated more than once.

16

Page 17: PRACTICAL COMMON LISP

PLUGGING THE LEAKS We can fix the multiple evaluation easily enough; we just need to

generate code that evaluates end once and saves the value in a variable to be used later.

For example, we can fix the multiple evaluation problem with this definition:(defmacro do-primes ((var start end) &body body) `(do ((ending-value ,end) (,var (next-prime ,start) (next-prime (1+ ,var)))) ((> ,var ending-value)) ,@body)) Recall that in a DO loop, variables defined with an initialization form

and no step form don't change from iteration to iteration. Unfortunately, this fix introduces two new leaks to the macro

abstraction.

17

Page 18: PRACTICAL COMMON LISP

PLUGGING THE LEAKS First new leak:

Because the initialization forms for variables in a DO loop are evaluated in the order the variables are defined, when the macro expansion is evaluated, the expression passed as end will be evaluated before the expression passed as start, opposite to the order they appear in the macro call.

This leak is trivially plugged by swapping the order of the two variable definitions.

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

18

Page 19: PRACTICAL COMMON LISP

PLUGGING THE LEAKS Second new leak:

The leak was created by using the variable name ending-value. The problem is that the name can end up interacting with code

passed to the macro or in the context where the macro is called. The following seemingly innocent(無害的 ) call to do-primes doesn't

work correctly because of this leak: (do-primes (ending-value 0 10) (print ending-value)) MACROEXPAND-1 can show the problem. > (macroexpand-1 '(do-primes (ending-value 0 19) (print

ending-value)))(do ((ending-value (next-prime 0) (next-prime (1+ ending-value))) (ending-value 10)) ((> ending-value ending-value))(print ending-value))

Some Lisps may reject this code because ending-value is used twice as a variable name in the same DO loop.

If not rejected outright, the code will loop forever since ending-value will never be greater than itself.

19

Page 20: PRACTICAL COMMON LISP

PLUGGING THE LEAKS Second new leak:

The following is another example:(let ((ending-value 0)) (do-primes (p 0 10) (incf ending-value p)) ending-value)

Again, MACROEXPAND-1 can show the problem. (let ((ending-value 0)) (do ((p (next-prime 0) (next-prime (1+ p))) (ending-value 10)) ((> p ending-value)) (incf ending-value p)) ending-value)

In this case the generated code is perfectly legal, but the behavior isn't at all what you want.

Because the binding of ending-value established by the LET outside the loop is shadowed by the variable with the same name inside the DO, the form (incf ending-value p) increments the loop variable ending-value instead of the outer variable with the same name, creating another infinite loop.

20

> (setf x 5)5> (incf x 0.3)5.3

Page 21: PRACTICAL COMMON LISP

PLUGGING THE LEAKS Clearly, what we need to patch this leak is a symbol that will never be

used outside the code generated by the macro. The function GENSYM returns a unique symbol each time it's called.

Thus, we can generate a new symbol each time do-primes is expanded.

(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)))

21

Page 22: PRACTICAL COMMON LISP

PLUGGING THE LEAKS With this definition the two previously problematic forms expand into code

that works the way you want. The first form:

(do-primes (ending-value 0 10) (print ending-value))expands into the following:(do ((ending-value (next-prime 0) (next-prime (1+ ending-value))) (#:g2141 10)) ((> ending-value #:g2141)) (print ending-value)) Now the variable used to hold the ending value is the gensymed symbol,

#:g2141. The name of the symbol, G2141, was generated by GENSYM but isn't

significant; the thing that matters is the object identity of the symbol.

22

Page 23: PRACTICAL COMMON LISP

PLUGGING THE LEAKS The other previously problematic form:

(let ((ending-value 0)) (do-primes (p 0 10) (incf ending-value p)) ending-value)looks like this if you replace the do-primes form with its expansion:(let ((ending-value 0)) (do ((p (next-prime 0) (next-prime (1+ p))) (#:g2140 10)) ((> p #:g2140)) (incf ending-value p)) ending-value) Now, there's no leak since the ending-value variable bound by the LET

surrounding the do-primes loop is no longer shadowed by any variables introduced in the expanded code.

23

Page 24: PRACTICAL COMMON LISP

PLUGGING THE LEAKS The rules to write a macro:

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.

24

Page 25: PRACTICAL COMMON LISP

Let’s write a macro SHOWVAR that displays the value of a variable, like this:(defun f (x y) (showvar x) (showvar y) (* x y))

> (f 3 7)The value of X is 3The value of Y is 721

(defmacro showvar (var) ‘(format t "~&The value of ~S is ~S” ’,var ,var))

25

Page 26: PRACTICAL COMMON LISP

If a template element is preceded by a comma and an at sign (,@), the value of that element is spliced into the result that backquote constructs rather than being inserted.

The value of the element must be a list. If only a comma is used, the element would be inserted as a single object,

resulting in an extra level of parentheses.

For examples:(setf name 'fred)(setf address '(16 maple drive))

> `(,name lives at ,address now) ;Inserting.(FRED LIVES AT (16 MAPLE DRIVE) NOW)> `(,name lives at ,@address now) ;Splicing.(FRED LIVES AT 16 MAPLE DRIVE NOW)

26

Page 27: PRACTICAL COMMON LISP

Write a macro SET-MUTUAL that takes two variable names as input and expands into an expression that sets each variable to the name of the other. (SET-MUTUAL A B) should set A to ’B, and B to ’A.

What’s the meaning of the following macro?(defmacro set-zero (&rest variables) `(progn ,@(mapcar #'(lambda (var) (list 'setf var 0)) variables) '(zeroed ,@variables)))

> (set-zero a b c)?

27