[plt-scheme] multiple invocation via require-for-syntax

From: Felix Klock's PLT scheme proxy (pltscheme at pnkfx.org)
Date: Tue Jul 29 00:28:06 EDT 2003


I'm actually making my way through the "Composable and Compilable 
Macros" paper now (again) in an effort to figure out how best to 
express what I want.  I have to admit that the paper does state in its 
explanation of figure 1 that require-for-syntax does cause 
instantiation of a module and those instantiations are not shared, even 
among different occurrences of the require-for-syntax expression during 
the same phase.  (Still, its not clear from that explanation that the 
separate instantiations are a part of the inherent semantics of 
require-for-syntax, and not just a side-effect of separate compilation, 
which is what I assumed at first, and thus was confused by because I 
thought that separate compilation would not be an issue in my example 
code.  Then again, the whole point of the paper is to make the 
semantics the same regardless of whether is dealing with separate 
compilation or not...)  I would still argue that the MzScheme language 
manual itself is misleading; I already quoted and referenced the 
relevant paragraph in the my last e-mail.

In response to your suggested change of code, the mutate! of "var" at 
syntax-expansion time was a deliberate choice in the example code I 
attached.  Its meant to represent some mutation of shared global state 
at expansion time.  Its not good enough to delay the mutation to 
runtime; I'm trying to share data between the transformations of two 
separate modules.

In the code I'm porting, the shared state is a list of 
class-descriptors (its an OOP method dispatch system used within the 
Edwin source base).  There is a DEFINE-CLASS construct that declares a 
[sub]class with a list of slots; so the macro expander looks up the 
superclass identifier in the list of class-descriptors so that it can 
determine what offset the newly added slots will be at in each 
instantiated object (represented as a vector of slots).  This offset 
calculation needs to happen at expansion time, because the offsets are 
later used during expansion to replace occurrences of slot-identifiers 
with slot-offsets.

It seems like MzScheme isn't going to be flexible enough for the most 
direct port of the OOP system; that's okay, I'm willing to make 
sacrifices, especially if such sacrifices make the code easier to 
understand.  (This whole exercise is a learning experience, after all)  
I'm currently planning to get around the limitation by dividing up the 
system into the following proposed hierarchy:

       (define class-descriptors '())
       (define (make-class ...) ... (set! class-descriptors ...) ...)
       (define-syntax define-class ...(make-class ...) ...)
    [module CLASS-DEFINITIONS mzscheme
        (define-class vanilla-window () ...)
        (define-class buffer-window vanilla-window ...)
       (require-for-syntax CLASS-DEFINITIONS)
       (define-syntax define-method ...)
    [module OOP-CLIENT-1 (require CLASS-USAGE-MACROS) (make-obj ...) ... 
(method-call ... ) ...]
    [module OOP-CLIENT-2 (require CLASS-USAGE-MACROS) (make-obj ...) ... 
(method-call ... ) ...]

My understanding is that there will be a separate instantiation of 
there will be an instantiation for each OOP-CLIENT in the above 
pseudo-scheme].  As long as the global list of class -descriptors is 
shared amongst the various class-definitions, then the slot-offsets 
will be assigned appropriately and the expansions in OOP-CLIENT-1 will 
be internally consistent.  I'm hoping that there won't be problems 
integrating the various OOP-CLIENTs with each-other; I'll have to cross 
that bridge when I come to it...

For comparison, the current (plt-incompatible) hierarchy looks more 
like this:

    [module CLASS-FUNDAMENTALS mzscheme
      (define class-descriptors '())
      (define (make-class ...) ... (set! class-descriptors ...) ...)
    [module CLASS-MACROS mzscheme
       (require CLASS-FUNDAMENTALS)
       (define-syntax define-class ... (make-class ...) ...)
       (define-syntax define-method ...)
    [module OOP-CLIENT-1 (require CLASS-MACROS)
         (define-class vanilla-window () ...)
         (make-obj ...) ...
         (method-call ...) ... ]
    [module OOP-CLIENT-2 mzscheme
         (require CLASS-MACROS)
         (require OOP-CLIENT-1)
         (define-class buffer-window vanilla-window ...)
         (make-obj ...) ...
         (method-call ...) ... ]

So you see, OOP-CLIENT-1 defines classes, which should alter the list 
of class-descriptors, which should modify the expansion-time 
transformation of OOP-CLIENT-2 (since it has a class which extends a 
class defined in OOP-CLIENT-1).  I prefer the current hierarchy since 
it seems to modularize the various aspects of functionality better; 
class definitions are kept in the same module as their method 
implementations and the method uses.  In the proposed hierarchy I gave 
above, the class definitions are separate from the method definitions 
and from the usage points.  I may have to shift the method definitions 
up to the higher level as well (not sure about this yet).  There may be 
ways to hack around the separation of definition from usage points, but 
before I can get to the point where I can put everything back together 
the way it was, I need to tear down the existing source base and build 
it back up again with a foundation that works...

If you like, I could attach the original source code from the edwin 
source base, or the currently hacked up version I have, but neither 
will be terribly useful in its current form (at least for executing in 
DrScheme; if all you want to do is read it, I'll be happy to toss it to 
you, or post it on a public webpage somewhere...)

Thanks for the explanation and for the advice,

On Tuesday, July 29, 2003, at 12:29  AM, Matthew Flatt wrote:

> At Mon, 28 Jul 2003 13:20:06 -0400, "Felix Klock's plt proxy" wrote:
>> This last paragraph implies (to me) that the single-invocation rule
>> should apply to both modules instantiated with REQUIRE-FOR-SYNTAX.
>> That is,
>> (module A ...)
>> (module B mzscheme (require-for-syntax a) ...)
>> (module C mzscheme (require-for-syntax a) ...)
>> (module D mzscheme (require b c) ...)
>> One would think that B and C would share the same copy of the A module
>> at syntax-expansion time.
> They share the same A instance while compiling D. But, compiling B then
> C then D will result in three separate instantiations of A --- one for
> each module compilation.
> I think that's the problem in your example. Apologies if I've misread
> your code, but I think the problem will be solved by having macros such
> as `b-syntax-mutate!' expand into a compile-time expression that uses
> `mutate!' instead of having `b-syntax-mutate!' call `mutate!' directly:
>  (b-syntax-mutate! FOO)
>  =>
>   (begin
>     (define-syntaxes () (mutate! ...))
>     FOO)
> If I'm on track, this is what 3.2.2 of the "Composable and Compilable
> Macros" paper is trying to explain.
> Matthew

Posted on the users mailing list.