[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: structures at expansion time



Quoting Ryan Culpepper:
> I'm trying to write a library which stores expansion-time information
> in identifiers (like signatures contain a vector of symbols) for
> access by several different modules. There is one core module which
> provides syntax for building information. The core module is required
> by several others which use this syntax. Since the information is
> somewhat complex, I was trying to store it in structures. The
> structure definitions are in another module require-for-syntax'd by
> the core module.

You might mean either of two things:

 1. your macro generates in instance of the structure, or

 2. your macro generates an expression that instantiates the structure.

#1 doesn't work. #2 does.

Here's a program to illustrate #1:

--------------------
(module d mzscheme
  (provide (struct d (x)))

  (define-struct d (x)))

(module s mzscheme
  (provide s)
  (require-for-syntax d)

  (define-syntax (s stx)
    (syntax-case stx ()
      [(_ var)
       (with-syntax ([val (make-d 10)])
	 (syntax (define-syntax var val)))])))

(module a mzscheme
  (require s)
  (provide instance)

  (s instance))

(module b mzscheme
  (require a)
  (require-for-syntax d)

  (t instance)

  (define-syntax (t stx)
    (syntax-case stx ()
      [(_ var)
       (let ([val (syntax-local-value (syntax var))])
	 (printf "instance?: ~a~n" (d? val))
	 #'(void))])))

--------------------

The above program prints "instance?: #f".

But by changing line 13 to

       (with-syntax ([val (syntax (make-d 10))])
                          ^^^^^^^            ^

we get "instance?: #t".

I'm guessing that you have a program like the original one, and you want
a program like the revised one.

The difference is that the original program produces "three
dimensional" code; a use of `s' expands to something with no textual
analogue:

  (define instance #<stuct:d>)

The revised code expands instead to

  (define instance (make-d 10))

and that's essentially why it works.

A good argument could be made for signalling an error when a program
attempts to produce 3-D syntax objects. But PLT Scheme doesn't signal
an error because 3-D syntax can be handy on occassion. (For example,
errortrace performs a 3-D transformation on syntax.)

> However, it seems that the structure definitions are re-evaluated
> every time the core module is required, so information built in one
> module cannot be accessed by another (because the accessors don't
> match the right structure type).

It's not every time the module is required; but a required-for-syntax
module is invoked every time is is used to expand a different module.

Thus, in the example above, `s' is invoked once to expand `a', and
again to expand `b'. The revised example works because the
expansion-time portion of `a' is also executed afresh when `b' is
expanded (so the `make-d' call happens again as `b' as expanded, not
just once as `a' is compiled).

> While I think I understand why the module system works this way (to
> make separate compilation consistant?),

Right. It's all designed to make you post to plt-scheme now, instead of
much later when you're trying to push the code through mzc. :)

(The plt-scheme post, in turn, helps me figure out how to better
explain and document things.)

> it seems to imply that
> expansion-time information (to be accessed by syntax-local-value)
> cannot usefully contain anything but built-in data types.
> [...]
> Is there any way to get this to work, or do I need to take a
> different approach?

If I'm guessing correctly, you'll probably be able to fix your code
easily.

`define-signature' and `unit/sig' rely on a structure for signature
information. The same is true of EoPL's `define-datatype', which
cooperates with `cases'.

In fact, even `syntax-case' and `syntax' are implemented as macros, and
there's a private struct type for pattern-varable bindings. A
`syntax-case' expression

  (syntax-case stx ()
    [a (syntax a)])

expands to something like

 (let ([gensymed1 stx])
  (let-syntax ([a (make-pattern-var #'gensymed1)])
     (syntax a)))

where the `syntax' macro detects that `a' is bound at expansion time to
an instance of `pattern-var', and the `pattern-var' instance indicates
that the run-time value for `a' will be stored in `gensymed1'.

Matthew