[plt-scheme] composing syntax-case macros?

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Fri Jan 16 21:12:39 EST 2009

YC wrote:
> On Fri, Jan 16, 2009 at 3:52 PM, Ryan Culpepper <ryanc at ccs.neu.edu> wrote:
>> ;; the non-working cond-it
>>> (define-syntax (cond-it stx)
>>>  (syntax-case stx (else)
>>>    ((cond-it (else exp exp2 ...))
>>>     #'(begin exp exp2 ...))
>>>    ((cond-it (test? exp exp2 ...))
>>>     #'(when-it test? exp exp2 ...))
>>>    ((cond-it (test? exp exp2 ...) cond1 cond2 ...)
>>>     #'(if-it test? (begin exp exp2 ...)
>>>              (cond-it cond1 cond2 ...)))))
>> When 'cond-it' expands and produces an 'if-it' expression, the 'if-it' is
>> marked by the macro expander as coming from a macro. That means its lexical
>> context is different from the 'it' variables in the branches. That means
>> that the 'it' variable binding produced by 'if-it' does not capture the 'it'
>> references in the branches.
> I see - based on this I tried to see if I can mark `if-it` to be the same
> lexical context but i get a compiler warning instead...
> (define-syntax (cond-it stx)
>   (syntax-case stx (else)
>     ((~) #'(void))
>     ((~ (else exp exp2 ...))
>      #'(begin exp exp2 ...))
>     ((~ (test? exp exp2 ...) cond1 ...)
>      (with-syntax ((if-it (datum->syntax #'~ 'if-it)))
>        #'(if-it test? (begin exp exp2 ...)
>                 (cond-it cond1 ...))))))
>> (cond-it (#f 'hello) ("test me" it) (else 'nothing))
> compile: identifier used out of context in: if-it
> I guess the app position cannot not be "captured" this way?

That approach has a flaw that has nothing to do with the error you got. 
Suppose that you write a module that imports just 'cond-it', not 
'if-it'. Or suppose that your module renames them or prefixes their 
names when you import them. Then when your macro creates the 'if-it' 
identifier, it will have the context of that module, where 'if-it' isn't 
bound. You're trying to use 'if-it' to carry the lexical context for the 
'it' binding it introduces, but its lexical context is already doing 
work---it tells the macro expander where to look up the 'if-it' macro.

This is why macros like 'if-it' are much more problematic than macros 
like 'define-struct', which is also unhygienic. The 'define-struct' 
macro also creates bindings of names it generates with 'datum->syntax'; 
for example,
   (define-struct circle (radius color))
defines 'make-circle', 'circle-radius', etc. But the lexical context for 
those names comes from the struct name ('circle'), which isn't acting as 
a reference. If the lexical context for the introduced names came from 
the 'define-struct' keyword instead, it would have all the same problems 
as 'if-it'.

> The macro stepper will show you this using colors. Try an example. The
>> original code is in black. The parts introduced by 'cond-it' are in a
>> different color (like red or blue).
> I'm working in emacs so I don't have macro stepper handy at the moment - is
> there a way to call macro stepper (a cmdline or repl version) from mzscheme?

Yes, although it's not nearly as nice as the one in DrScheme. Search the 
help desk for 'expand/step-text'.

> The problem comes from having macros introduce unhygienic bindings of 'it'.
>> A better way to do it would be to bind 'it' to a syntax parameter and update
>> the meaning of 'it' in the expansion of an 'if-it' expression (using
>> 'syntax-parameterize').
> This is macro voodoo! ;)  I'll have to learn more about syntax parameters
> and transformers.  Is there some sort of guide docs that talks about how and
> why (usages) on these capabilites?

I wrote a blog post about syntax parameters a couple years ago:

Other than that, the Guide and the Reference.

I'm also working on a collection of macro techniques and design 
patterns. I hope to post a draft of it sometime in the near future.

> Thansks for demonstrating the cool technique and the clear explanation.

Glad to help.


Posted on the users mailing list.