From posting-system@google.com Tue Mar 5 16:46:58 2002 Date: Tue, 5 Mar 2002 13:46:54 -0800 From: oleg@pobox.com (oleg@pobox.com) Newsgroups: comp.lang.scheme Subject: Re: An Advanced Syntax-Rules Primer for the Mildly Insane References: <98e00fa4.0202280351.1083aac7@posting.google.com> <87it8db0um.fsf@radish.petrofsky.org> Message-ID: <7eb8ac3e.0203051346.230cb83d@posting.google.com> Status: OR Al Petrofsky wrote a great article http://groups.google.com/groups?selm=87it8db0um.fsf%40radish.petrofsky.org That's a very interesting and detailed article! I'd like to mention two somewhat subtle pitfalls we have to be aware of when writing R5RS macros. I have fallen into both these pitfalls. Hopefully my example will save someone from doing the same. The examples are a bit simplified for presentation; yet, the pitfalls are very real. The first pitfall concerns with lexical scoping and shadowing. Pattern variables don't shadow! Let's consider a simple function: (define foo-f (lambda (x) (let ((id (lambda (x) x))) (id (+ 1 x))))) (display (foo-f 4)) ; prints 5 (newline) The argument of 'foo-f' is named x; the argument of 'id' is also named 'x'. There is no confusion as the latter shadows the former. Suppose we want to re-write foo-f as a macro (e.g., for efficiency). We can do that literally: (define-syntax foo-m (syntax-rules () ((_ x) (let-syntax ((id (syntax-rules () ((_ x) x)))) (id (+ x 1)))))) (display (foo-m 4)) (newline) oops, we've got an error: "Use of macro does not match definition -- ((id~1 (+~1 4 1)))" Let's try to manually expand (foo-m 4) to see what's going on. On the first iteration, (foo-m 4) expands into (let-syntax ((id (syntax-rules () ((_ 4) 4)))) (id (+ 4 1))) Pattern variable 'x' matched the number 4. Therefore, all instances of 'x' in the template are replaced by 4. Absolutely all instances! When the macroexpander gets to (id (+ 4 1)) it finds out that the expression doesn't match the only given pattern (_ 4). Therefore, the macro-expander reports an error. Getting an error is the best possible case. In the worst case, no error is generated but the result of the macro-expansion is just bizarre. To fix the problem, we should always make sure that all unrelated pattern variables have unique names: (define-syntax foo-m-fixed (syntax-rules () ((_ x) (let-syntax ((id (syntax-rules () ((_ y) y)))) (id (+ x 1)))))) (display (foo-m-fixed 4)) ; prints 5 Pattern variables don't shadow each other! The second pitfall deals with passing parameters to a submacro. You can do it through arguments, or through the "environment". Better make this _exclusive_ OR. Do not mix the two techniques! The pitfall has been covered in the original article by Al Petrofsky. I'll repeat it again on a simpler example. (define (bar-f x y) (let ((helper (lambda (u) (+ x u)))) (helper y))) (display (bar-f 4 1)) ; prints 5 The body of a 'subfunction' helper sums the values of two variables: x and u. The value of 'x' is taken from the lexical environment that encloses the 'helper' -- namely, the lexical environment of bar-f's body. The value of 'u' is passed to the helper as an argument. Suppose we want to re-write bar-f as a macro: (define-syntax bar-m (syntax-rules () ((_ x y) (let-syntax ((helper (syntax-rules () ((_ u) (+ x u))))) (helper y))))) (display (bar-m 4 1)) ; prints 5 It works. Where's the pitfall then? Let us consider a very similar macro: (define-syntax bar-m2 (syntax-rules () ((_ var body) (let-syntax ((helper (syntax-rules () ((_ bdy) (lambda (var) bdy))))) (helper body))))) (display ((bar-m2 z (+ z 1)) 4)) (newline) When we evaluate the display form we get a run-time error: "Unbound variable -- z". Al Petrofsky explained in great detail why this happens. To fix the problem, we have to pass the parameters to the submacro 'helper' either through the "environment": (define-syntax bar-m1 (syntax-rules () ((_ var body) (let-syntax ((helper (syntax-rules () ((_) (lambda (var) body))))) (helper))))) (display ((bar-m1 z (+ z 1)) 4)) ; prints 5 or through the arguments: (define-syntax bar-m3 (syntax-rules () ((_ var body) (let-syntax ((helper (syntax-rules () ((_ vvar bbody) (lambda (vvar) bbody))))) (helper var body))))) (display ((bar-m3 z (+ z 1)) 4)) ; prints 5 Again, don't mix the ways of passing parameters to a submacro! If you're mindful of these pitfalls and use CPS/tail-calls, you will find that -- surprisingly -- writing R5RS macros looks a lot like writing regular tail-recursive Scheme procedures.