[plt-scheme] composing syntax-case macros?

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Fri Jan 16 18:52:30 EST 2009

On Jan 16, 2009, at 5:33 PM, YC wrote:

> Hi all -
>
> I am running into issues composing a couple of syntax-case macros,  
> and not sure where things go wrong. When I try to write a `cond-it`  
> using an working `if-it` and `when-it`, I was unable to capture `it`  
> within `cond-it`.

It is difficult to write macros that expand into unhygienic macros.

More below...

> ;; the working if-it & when-it
>
> (define-syntax (if-it stx)
>   (syntax-case stx ()
>     ((if-it test? then else)
>      (with-syntax ((it (datum->syntax #'if-it 'it)))
>        #'(let ((it test?))
>            (if it then else))))))

Note: the lexical context of the introduced 'it' variable comes from  
the 'if-it' macro keyword.

If the 'if-it' identifier and the references to 'it' in the branch  
expressions are both part of the original program, then they have the  
same lexical context and the introduced 'it' captures the references  
to 'it'.

> (define-syntax (when-it stx)
>   (syntax-case stx ()
>     ((~ test? exp exp2 ...)
>      (with-syntax ((it (datum->syntax #'~ 'it)))
>        #'(let ((it test?)) (when it exp exp2 ...))))))
>
> ;; 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.

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 tried to capture the `it` binding as well - but it still doesn't  
> work.
>
>
> (define-syntax (cond-it stx)
>   (syntax-case stx (else)
>     ((cond-it (else exp exp2 ...))
>      #'(begin exp exp2 ...))
>     ((cond-it (test? exp exp2 ...))
>      (with-syntax ((it (datum->syntax #'cond-it 'it)))
>        #'(when-it test? exp exp2 ...)))
>     ((cond-it (test? exp exp2 ...) cond1 cond2 ...)
>      (with-syntax ((it (datum->syntax #'cond-it 'it)))
>        #'(if-it test? (begin exp exp2 ...)
>                 (cond-it cond1 cond2 ...))))))

The 'it' pattern variable above isn't used in the syntax template. It  
has no effect on the code you produce.

One thing you could do is put in the code to patch one 'it' into the  
other 'it'. Here's the code. (I've also simplified the patterns a  
little).

(define-syntax (cond-it stx)
   (syntax-case stx (else)
     ((cond-it)
      #'(void))
     ((cond-it (else exp exp1 ...))
      #'(begin exp exp1 ...))
     ((cond-it (test? exp ...) cond1 ...)
      (with-syntax ((original-it (datum->syntax #'cond-it 'it)))
        #'(if-it test?
                 (let ([original-it it])
                   (begin (void) exp ...))
                 (cond-it cond1 ...))))))

I recommend using the macro stepper to see why this works.

The problem is, this is really fragile. Try changing the occurrences  
of 'cond-it' in the syntax patterns to '_' instead, and watch it break.

> When I finally write `cond-it` without using `if-it` and `when-it`,  
> it worked.
>
> (define-syntax (cond-it stx)
>   (syntax-case stx (else)
>     ((cond-it (else exp exp2 ...))
>      #'(begin exp exp2 ...))
>     ((cond-it (test? exp exp2 ...))
>      (with-syntax ((it (datum->syntax #'cond-it 'it)))
>        #'(let ((it test?)) (when it exp exp2 ...))))
>     ((cond-it (test? exp exp2 ...) cond cond2 ...)
>      (with-syntax ((it (datum->syntax #'cond-it 'it)))
>        #'(let ((it test?))
>            (if it (begin exp exp2 ...)
>                (cond-it cond cond2 ...)))))))
>
> What am I missing here?  How can `cond-it` be composed of `if-it`  
> and `when-it`?

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'). Here's the code:

;; Warning: entering PLT-specific macrology zone :)

#lang scheme
(require scheme/stxparam)

(define-syntax-parameter it
   (lambda (stx) (raise-syntax-error #f "illegal use" stx)))

(define-syntax if-it
   (syntax-rules ()
     [(if-it test then else)
      (let ([temp test])
        (syntax-parameterize ([it (make-rename-transformer #'temp)])
          (if temp then else)))]))

(define-syntax cond-it
   (syntax-rules (else)
     ((cond-it)
      (void))
     ((cond-it (else exp exp1 ...))
      (begin exp exp1 ...))
     ((cond-it (test? exp ...) cond1 ...)
      (if-it test?
             (begin (void) exp ...)
             (cond-it cond1 ...)))))

Ryan

> _________________________________________________
>  For list-related administrative tasks:
>  http://list.cs.brown.edu/mailman/listinfo/plt-scheme



Posted on the users mailing list.