[plt-scheme] Best practices when writing macros; avoid name shadowing?

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Mon Feb 4 16:42:23 EST 2008

On Feb 3, 2008, at 11:02 PM, Grant Rettke wrote:

> Tonight I was working on a problem from
> _The_Scheme_Programming_Language_3rd_Edition,
> in particular Exercise 3.2.5 in
> http://www.scheme.com/tspl3/further.html#./further:h2.
>
> In this problem you goal is to augment a given version of `let` to
> handle `named let` using letrec.
>
> I worked on the problem and came up with solution that made sense and
> according to my rudimentary
> testing worked. The expanded code was what I expected to see, and that
> code did what I expected. When I checked
> the answer, it was wrong, but only by a bit, and I didn't understand
> why since it worked. The error was later demonstrated
> to me.
>
> Here is the source:
>
> (custom-let ([fun '(a b c)])
>              (custom-let fun ([ls fun])
>                         (unless (null? ls)
>                           (printf "~s\n" (car ls))
>                           (fun (cdr ls)))))
>
> My wrong version of expanded code:
>
> ((lambda (fun)
>      (letrec ([fun (lambda (ls)
>                      (unless (null? ls)
>                        (printf "~s\n" (car ls))
>                        (fun (cdr ls))))])
>        (fun fun))) '(a b c))
>
> The correct way to expand the code:
>
> ((lambda (fun)
>      ((letrec ((fun (lambda (ls)
>                       (unless (null? ls)
>                         (printf "~s\n" (car ls))
>                         (fun (cdr ls)))))) fun)
>       fun)) '(a b c))
>
> I see the error, the function name in the letrec block shadows the
> name of the list in the enclosing lambda block.
>
> Is this a lesson that you learn writing macros, to be sure not to
> shadow variables in your expansion?

When you write a macro, you should think about what parts of the input 
are expressions and what context you use them in the macro result. In 
particular, here are a couple questions you should ask about each 
expression:
  - What bindings are in scope where it occurs? (Hygiene makes it 
feasible to answer this question.)
  - How many times does it occur in the expansion? (More than once is 
almost always a bug.)
  - When is it evaluated?
  - How often is it evaluated?
  - What dynamic context (exception handlers, parameterization, thread, 
etc) is it evaluated in?

For the named let example, what expressions should be in the scope of 
what bindings? Try looking at the two versions of your macro. If you 
look carefully at the expressions one by one, it should be 
straightforward to tell which one satisfies the requirements and which 
one doesn't (and why).

That's the basic idea. There are a few more questions when you get into 
more complex constructs (like modules, classes, etc).

Ryan

> There must be tons of hairy situations that folks who heavily utilize
> the macro system have to avoid!
>
> All thoughts appreciated on this one.
> _________________________________________________
>   For list-related administrative tasks:
>   http://list.cs.brown.edu/mailman/listinfo/plt-scheme



Posted on the users mailing list.