#| Lecture Notes: Macros Adapted from http://aplcenmp.apl.jhu.edu/~hall/ (For more notes on macros, see Graham p162 to 172) Macros in Lisp provide a very powerful and flexible method of extending Lisp syntax. Lisp functions take Lisp values as input and return Lisp values. They are executed at run-time. Lisp macros take Lisp code as input, and return Lisp code. They are executed at compiler pre-processor time, just like in C. The resultant code gets executed at run-time. 1. Basic Idea: Macros take unevaluated Lisp code and return a Lisp form. This form should be code that calculates the proper value. Example: |# (defmacro Square-1 (X) `(* ,X ,X)) #| Wherever the pre-processor sees (Square-1 XXX) to replaces it with (* XXX XXX). The resultant code is what the compiler sees. TRAPS FOR BEGINNERS ==================== (A) Trying to evaluate arguments at compile time (B) Evaluating arguments too many times (C) Variable name clashes. COMPILE TIME EVAL ERROR ----------------------- Macros are expanded at compiler pre-processor time. So this is an error: |# (defmacro Square-2 (X) (* X X)) #| This would indeed work for (Square%% 4), but would crash for (Square%% X), since X is probably a variable whose value is not known until run-time. Evaluating arguments too many times ----------------------------------- |# (defmacro Square-1 (X) `(* ,X ,X)) #| This looks OK on first blush. However, try macroexpand-1'ing a form, and you notice that it evaluates its arguments twice: (macroexpand-1 '(Square-1 (Foo 2))) ==> (* (Foo 2) (Foo 2)) Foo gets called twice, but it should only be called once. Inefficient. Also, returns the wrong value if Foo does not always return the same value. e.g. (Square-1 (incf X)) e.g. (Square-1 (random 10)) Fix: |# (defmacro Square-3 (X) '(let ((Temp ,X)) (* Temp Temp))) #| (macroexpand-1 '(Square-3 (Foo 2))) ==> (let ((Temp (Foo 2))) (* Temp Temp)) This is what we want. Variable name clashes. -------------------------- Square-3 is perfectly safe, but consider instead the following macro, which takes two numbers and squares the sum of them: |# (defmacro Square-Sum-1 (X Y) '(let* ((First ,X) (Second ,Y) (Sum (+ First Second))) (* Sum Sum)) ) #| This looks pretty good, even after macroexpansion: (macroexpand-1 '(Square-Sum-1 3 4)) ==> (LET* ((FIRST 3) (SECOND 4) (SUM (+ FIRST SECOND))) (* SUM SUM)) which seems ok. BUT the local variables we chose would conflict with existing local variable names if a variable named First already existed. E.g. (macroexpand-1 '(Square-Sum-1 1 First)) ==> (LET* ((FIRST 1) (SECOND FIRST) (SUM (+ FIRST SECOND))) (* SUM SUM)) The problem here is that (SECOND FIRST) gets the value of the new local variable FIRST, not the one you passed in. Thus (let ((First 9)) (Square-Sum-1 1 First)) returns 4, not 100! Solution: need to create a variable name inside the macro that cannot exist anywhere else in the code. |# (defmacro Square-Sum (X Y) (let ((First (gensym "FIRST-")) (Second (gensym "SECOND-")) (Sum (gensym "SUM-"))) '(let* ((,First ,X) (,Second ,Y) (,Sum (+ ,First ,Second))) (* ,Sum ,Sum)) )) #| Now (macroexpand-1 '(Square-Sum 1 First)) ==> (LET* ((#:FIRST-590 1) (#:SECOND-591 FIRST) (#:SUM-592 (+ #:FIRST-590 #:SECOND-591))) (* #:SUM-592 #:SUM-592)) This expansion has no dependence on any local variable names in the macro definition itself, and since the generated ones are guaranteed to be unique, is safe from name collisions. Just to complete the picture, there is the "real" definition of square: |# (defmacro square (x) (let ((temp (gensym))) `(let ((,temp ,x)) (* ,temp ,temp))))