Macros ------ It would be nice to be able to define new special forms in Tiny-Scheme -- functions that don't evaluate their arguments, or do so in a controlled way, like if and cond. For example, we may wish to have a special form unless such that (unless test body) evaluates test, and then only if the test is #f, evaluates and returns the value of body. We could use if or cond for this: we could write (if (not test) body #f) which does what we want. But (unless test body) looks better. It would be nice if we could define it. It would also be convenient to be able to change the values of variables in the current environment in a function call (call-by-reference or call-by-name). For example, we might want to have a function inc! that when applied to a variable x increments it; i.e., (inc! x) would have the same effect as (set! x (inc x)). Right now there is no way to do these things using the Tiny-scheme constructs we have seen. You might think the following definitions will work: (define inc! (lambda (x) (set! x (inc x)))) (define unless (lambda (test body) (if (not test) body #f))) but they don't. The definition of inc! above set!s the value of the local x (the parameter), not the variable whose value is passed to it. inc! cannot be written as a function since it needs access to the variable in the argument, not its value. (You could do this in C with pointers.) The definition of unless is also not right, because it will always evaluate body, regardless of the value of test, which is not what we want. --------------------------------------------------------------------- A possible solution is MACROS: Macros are like functions, except they don't evaluate their arguments, but textually substitute them unevaluated into the body, then evaluate the result. These special functions construct code out of other code -- operate on Scheme expressions as list structures. Macros *are* part of Swindle/Scheme. And we can add them to Tiny-Scheme Ordinary functions: 1. Evaluate the arguments 2. Apply function to (values of) the arguments 3. Return the result of step 2 Macros: 1. Substitute the (unevaluated) arguments *textually* in the body 2. Evaluate the result 3. Return the result of step 2. Basic idea (function): Example: (square x) The head is *not* a macro, so we evaluate square and x in current env Then apply [value of square] to [value of x] Basic idea (macro): Example: (inc! z) The head *is* a macro, so we create the body (set! z (inc z)) which is (set! x (inc x)) with the *unevaluated* z substituted for x, which we then evaluate ---------------------------------------------------------------------- We can add a new class and a new special form macro to Tiny-Scheme which allows us to create macros. It is implemented in a way that may seem strange at first but actually gives maximum flexibility. 1. The body is evaluated twice, once to do the textual substitution and once to compute the value. 2. The first evaluation is done in the environment of the closure, the second is done in the environment of the call. (This is usually what we want.) [Note: there are some subtle issues here!!] A macro object is created by evaluating (defmacro (params) pre-body) where pre-body is something that will evaluate to the desired body when the UNEVALUATED arguments are bound to the params. The resulting body will then be evaluated in the *original* environment (i.e., the environment of the macro call). For example, we want (inc! Z) to expand to (set! Z (inc Z)), and then we want that to be evaluated in the *current* environment. We would define (defmacro inc! (list 'set! x (list 'inc x)))) Here the pre-body is (list 'set! x (list 'inc x)) Now, if you type (inc! z) the unevaluated z will be bound to the parameter x of the macro and the pre-body will be evaluated in that environment, giving (set! z (inc z)) Then that expression will be evaluated in the *current* environment, which has the effect of incrementing z in the current environment. ----------------------------------------------------------------- Similarly, for unless, we would be define (define (unless ) (macro (tst bdy) (list 'if (list 'not tst) bdy '#f))) Then when we evaluate (unless (= x 0) (/ 2 x)) the pre-body (list 'if (list 'not tst) bdy '#f) is evaluated in an environment with bindings tst:(= x 0) and bdy:(/ 2 x), the unevaluated lists, to give (if (not (= x 0)) (/ 2 x) #f) ---that's the first evaluation---then that expression is evaluated in the current environment. ----------------------------------------------------------------- A macro gets expanded when its name appears as the first position of a compound expression, as in (inc! x), which looks just like an ordinary function application, except the thing in first position is a symbol naming the macro. Macro application calls a procedure to evaluate the macro body (which is the pre-body discussed above) in the environment of the closure with the macro parameters bound to the unevaluted arguments, then evaluates the result in the *calling* environment. ---------------------------------------------------------------------- OK, here's one subtlety (of many!) Suppose I want to write a function that prints out the name of the variable it's invoked with, together with its value. For example (define x 3) (display-and-eval x) ==> "The value of x is 3" (defmacro (display-and-eval var) `(let ((value ,var)) (display "The value of") (display ',var) (display "is") (display value))) One can do this with list, but backquote and comma make life a lot easier!! (LET ((LET '`(LET ((LET ',LET)) ,LET))) `(LET ((LET ',LET)) ,LET)) ---------------------------------------------------------------------- So, this lets us define new special forms. Try out inc! (define (x ) 12) Then (inc! x) ;; now x is 13 This is very much like procedure calls, except 1. Macro gets the text of the call, not the value 2. Macro is evaluated *twice*. ---------------------------------------------------------- WARNING: * Macros are incredibly dangerous! * They are almost never the right thing to use! Many languages have them * They can do things function calls can't. Here's C-style: C hackers are required by federal law to be efficiency freaks, * Macros in C save the cost of a function call * Which is pretty high in C. #define square(x) x*x For example, square(4) --expands-to--> 4*4 The parameter x is replaced by the TEXT of its actual argument, 4. But this is DOOMED: square(n+2) --enpands-to--> n+2*n+2 which parses as n+(2*n)+2, which is hardly ever equal to (n+2)*(n+2). In C -- not Scheme, Scheme fully parenthesized -- you have to write #define square(x) ((x)*(x)) and if you forget you are DOOMED. Even this doesn't help: Suppose Expensive(n) takes a week to compute. square(Expensive(n)) --expands-to--> ((Expensive(n))*(Expensive(n))) which means you call it *twice*, so it takes two weeks to compute. --> But, you did save the function call. Yay! Anyways, macros are too dangerous to use when you can avoid it. Basically should restrict use to defining new special forms. ---------------------------------------------------------------------- SUMMARY: * Think of macros as TEXTUAL SUBSTITUTION done BEFORE evaluator gets an expression to evaluate. * Macros greatly extend the expressive power of languages. - They are very powerful - They are very tricky to think about