[plt-scheme] multiple invocation via require-for-syntax
Matthew-
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:
[module CLASS-DEFINITION-FUNDAMENTALS mzscheme
(define class-descriptors '())
(define (make-class ...) ... (set! class-descriptors ...) ...)
(define-syntax define-class ...(make-class ...) ...)
...]
[module CLASS-DEFINITIONS mzscheme
(require CLASS-DEFINITION-FUNDAMENTALS)
(define-class vanilla-window () ...)
(define-class buffer-window vanilla-window ...)
...]
[module CLASS-USAGE-MACROS
(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
CLASS-DEFINITIONS for each time CLASS-USAGE-MACROS is REQUIRE'd [thus,
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,
-Felix
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