1 / 28

PRACTICAL COMMON LISP

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

Download Presentation

PRACTICAL COMMON LISP

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

  2. CHAPTER 8 MACROS: DEFINING YOUR OWN

  3. 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.

  4. 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))

  5. 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 19 NIL

  6. 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 (p019) (format t "~d " p))

  7. 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 anested 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))

  8. 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.

  9. 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))

  10. DEFINING A MACRO • 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

  11. DEFINING A MACRO • 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

  12. DEFINING A MACRO • 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

  13. DEFINING A MACRO • 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. • (defmacro simple-incf (var &optional (amount 1)) • (list ’setq var (list ’+ var amount)))

  14. DEFINING A MACRO • 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 > a 7 • The input to FAULTY-INCF is the number seven. • 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.

  15. Exercise s • 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.

  16. 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 19 NIL • 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.

  17. 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 CL-USER> (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.

  18. 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.

  19. 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))

  20. 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-primesdoesn't work correctly because of this leak: (do-primes (ending-value 0 10) (print ending-value)) • MACROEXPAND-1 can show the problem. Break 7 [16]> (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-valueending-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.

  21. 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.

  22. 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)))

  23. 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.

  24. 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.

  25. 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.

  26. Example • 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 3 The value of Y is 7 21 (defmacro showvar (var) ‘(format t "~&The value of ~S is ~S” ’,var ,var))

  27. SPLICING WITH BACKQUOTE • 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)

  28. Exercises • 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) ?

More Related