[racket] On Cooperating Macros

From: Ryan Culpepper (ryan at cs.utah.edu)
Date: Fri Nov 16 18:05:12 EST 2012

On 11/16/2012 05:16 PM, Ray Racine wrote:
> I've run across a few things mentioning cooperating macros, something
> I'm working through.
>
> Consider the following in a source file "nest.rkt"
> https://gist.github.com/4090877
>
> So in this example each expansion of the two defined macros in the
> `nest' module sees mutually mutable state at meta level 1.
>
> And of course in a separate source module, say "bird-watcher.rkt" with
> the following.
>
> #lang racket
>
> (require "nest.rkt")
>
> (define the-nest
>     (begin
>      (daddy-brings-worms)
>      (daddy-brings-worms)))
>
> the-nest
>
> ;; end-of-module
>
> One sees that the mutable state is not maintained across modules.  i.e.,
> Not cooperating.  All, I'm guessing, a direct consequence of the
> "Immutable Laws Of Flatt" (ILOF), "You Want It When", and "Modules,
> Visitations and Instantiations, Oh My".

Yes. To summarize: the expansion/compilation of each module starts with 
a fresh state, which is then initialized with the compile-time parts of 
the modules it depends on.

> BUT, the ILOF has a backdoor[1], there exists a meta level wormhole
> which allows state to tunnel through modules and expansions.  If one
> were to use `define-syntax' at compile time (during meta level 1) to
> bind a non-procedure value, one can rat-hole state which is subsequently
> accessible via `syntax-local-value' in an a different macro expansion at
> meta level 1, in a different module. i.e., the macros now can cooperate.
>   Here I'm taking "cooperate" as synonymous with shared mutable state.
>
> Based on what I think I understand, I can rejigger my bird net examples
> to cooperate using the wormhole.

No, that's not a back door, because you still get a fresh state and then 
you execute the definition again. So if you did something like

   (define-syntax the-nest-state (box (cons 0 0)))

and then mutated the box during the expansion of module A, then when you 
start expanding module B you execute that syntax definition again, so 
module B starts out with a fresh box holding (cons 0 0).

--

The *only* way for module A to affect the compilation of module B in any 
way is for module B to require (directly or indirectly) module A.

And the only way to make a compile-time side-effect in module A 
"persistent", so it will affect the compile-time state of other module 
compilations is to put it in a 'begin-for-syntax'. (Actually, there are 
a few other ways, but they take longer to explain.)

--

I'll stop here in case you want to reformulate the questions in your 
next few paragraphs based on my answers so far.

Ryan


> So assuming the above is more-or-less correct, can cooperating macros be
> accomplished solely via the application of module require / provide at
> proper meta levels.  One one hand, I'm visualizing a `define-syntax'
> binding a non-procedure value at compile time (meta 1) as in essence
> pushing the value up into meta 2.  And the `syntax-local-value' as
> reaching out from meta 1 into meta 2 to fetch the state down.  It's
> global because meta layer 2 is a single global environment in which meta
> 1 syntax transformers are evaluated.
>
> On the other, other hand I visualize that during module visitations and
> instantiations the _only_ thing which can _cross_ meta levels is syntax.
> i.e. syntax at meta level N can be lifted into meta level N+1,
> transformed and returned back to meta level N.  But as only syntax
> crosses meta boundaries, hence the define-syntax and syntax-local-value
> backdoor.
>
> Or another way of phrasing the question, "Can one
> write cooperating macros without resorting to the use of `define-syntax'
> and `syntax-local-value'".   If yes, how?
>
>
> Thanks,
>
>
> Ray
>
>
> [1] Seems the only true immutable law in CS is, there's always a backdoor.
>
>
> ____________________
>    Racket Users list:
>    http://lists.racket-lang.org/users
>


Posted on the users mailing list.