[plt-scheme] local-expansion interaction with class.ss

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Thu Jun 14 16:57:15 EDT 2007

On Jun 14, 2007, at 3:12 PM, Jim Meier wrote:

> I'm new to using mzscheme macros, and so I apologize if the answer to 
> these questions is as simple as pointer to the correct paragraph of 
> documentation.
>
> I'm trying to build an extension language on top of the standard 
> mzscheme that redefines #%app to preprocess the call through a simple 
> wrapper function:
>
> (module my-lang mzscheme
>   (define-syntax my-app
>     (syntax-rules ()
>       ((_ f a ...)
>        (app/wrapper f a ...))))
>
>   (define (app/wrapper fun . args)
>     ;; various processing
>     (apply fun args))
>
>   (provide (all-from-except mzscheme #%app))
>   (provide (rename my-app #%app))
> )
>
> This works well for many programs; however, if those programs use the 
> standard class.ss library, this causes issues:
>
> (module objects-test my-lang
>   (require (lib "class.ss"))
>
>   (define cls%
>     (class object%
>       (define/public (some-method a)
>         (printf "Method invoked: ~a\n" a))
>       (super-new)
>       (some-method "Implicit Send")))
> )
>
> Here, some-method is converted to a macro that only works in the 
> application position; so when the my-app macro rewrites (some-method 
> "Implicit Send") to (app/wrapper some-method "Implicit Send"), it 
> complains with:
>
>    class: misuse of method (not in application) in: some-method

You've encountered an unfortunate interaction between '#%app'-like 
syntax and the class system. I'll describe what's going wrong and give 
you a workaround, but the problem probably ought to be addressed in the 
'class' macro.

The 'class' macro needs to shallowly expand (using 'local-expand') the 
things in its body to uncover definitions and declarations for things 
like 'init', 'public', etc. It stops once it recognizes something as an 
expression, to avoid just this sort of problem. The problem is it 
doesn't know that your new version of '#%app' is an expression form 
until it does the expansion. It goes from this form:
   (some-method "Implicit Send")
to this:
   (#%app-from-mzscheme app/wrapper some-method "Implicit Send")
and then stops, but by then it has already messed up the method 
invocation syntax. Notice that if you change that last line in your 
class to this
   (void (some-method "Implicit Send"))
the problem goes away, because it stops here:
   (#%app-from-mzscheme app/wrapper void (some-method "Implicit Send"))
Since 'void' isn't a method, there's no problem.

Some kinds of macros protect themselves from contexts like 'class' by 
checking to see how they are being expanded. The 'class' macro expands 
its body in an internal definition context. Since your version of 
'#%app' doesn't produce definitions, you can do the following, which 
tells the macro expander "I'm an expression form---come back when 
you're ready to expand expressions."
     (define-syntax (my-app stx)
       (if (eq? (syntax-local-context) 'expression)
           (syntax-case stx ()
             ((_ f a ...)
              #'(app/wrapper f a ...)))
           #`(#%expression ,stx)))
Sadly, that technique doesn't quite work here, and that's because of 
the special handling of the "implicit" macro names like '#%app' and 
'#%datum'. When the expander sees something that looks like an 
application, it wraps a use of '#%app' (in the local environment) 
around it before passing it to the associated macro transformer. So the 
syntax received by your 'my-app' macro is this:
   (#%app some-method "Implicit Send")
and the trick above just turns it into this:
   (#%expression (#%app some-method "Implicit Send"))
Too late! The damage is already done.

So instead, you should knock the '#%app' off the front again:
   (define-syntax (my-app stx)
     (if (eq? (syntax-local-context) 'expression)
         (syntax-case stx ()
           ((_ f a ...)
            #'(app/wrapper f a ...)))
         (syntax-case stx ()
           ((app-keyword . application)
            #'(#%expression application)))))
There's your workaround.

The 'class' macro could do more to avoid this kind of problem. It could 
add declared method names (and init names and field names) to the stop 
list as it expanded, but that would fail to fix the problem when the 
method is declared after it is used. Still, I bet that change would 
address the problems that come up in practice.

Ryan



Posted on the users mailing list.