[plt-scheme] Attaching compile time information to identifiers

From: Ryan Culpepper (ryan_sml at yahoo.com)
Date: Sun Oct 29 18:54:45 EST 2006

Let me summarize and elaborate the responses the original poster has
gotten so far.

The OP wanted to attach compile-time information to identifiers.
There are two ways to do this. They both work.

1) Make the information part of the identifier's binding. (Eli)

Examples of this approach include static struct info, unit/sig
signatures, and match expanders. Defining a struct binds the struct
name to a compile-time list of the predicate, accessors, mutators,
etc. A signature is a compile-time structure containing a vector of
names. Finally, a match-expander is a compile-time procedure-like
structure: the match forms can recognize it and execute the
match-transformer, but it can also act as a normal macro, so you can
use it in normal expressions too.

Producers of such static information use "define-syntax". Enter the
following program in a recent nightly build of DrScheme, in the
Pretty Big Scheme language level:

  (define-struct A (x y))
  (define-signature pair^ (car cdr))

Then click "Macro Stepper". Click on the occurrence of
"define-struct", then right click and select "Show this macro". Look
at the "(define-syntaxes (A) ...)" part of the expansion. Go to the
next term. Select and show the "define-signature" macro.

Clients of such bindings use syntax-local-value to fetch the values
bound at compile-time to these names. Add the following to the end of
the program above:

(begin-for-syntax
  (display (syntax-local-value #'A))
  (newline)
  (display (syntax-local-value #'pair^))
  (newline))

Well, signatures are represented by opaque structs. But the unitsig
library has access to the predicate and selectors.

****

2) Make a separate table to contain the information, and update the
table each time information is associated with an identifier. (Sam,
Jens)

Sometimes you just want to attach extra information to an *existing*
binding. For example, if you intend to use a name in expression
contexts, it's useful to attach the extra information in a way that
doesn't interfere with the "define"d meaning of the variable.

As Jens pointed out earlier, Matthew Flatt's ICFP 2002 paper covers
the subtleties of this approach. Here's a summary for the purpose of
this problem:

  Side-effects that happen in a macro are transient; they live only
  as long as the compilation process. To attach information to an
  identifier, make the side-effect part of the program (module)
  itself.

So, you should modify the code Sam sent earlier. Change this:

  (define-syntax (f stx)
    (syntax-case stx ()
      [(f id args ...)
       (begin
         (module-identifier-mapping-put! mapping #'id #'(args ...))
         #'(void))]))

into this:

  (define-syntax (f stx)
    (syntax-case stx ()
      [(f id args ...)
       #'(begin
           (begin-for-syntax
             (module-identifier-mapping-put! mapping 
                                             #'id
                                             #'(args ...)))
           (void)]))

Now the instruction to update the table will be part of the
program/module. If this module is required by another module, its
begin-for-syntax forms are executed again, and the association is
known to the requiring module.

One last remark: if you use a table to attach information to an
identifier that is unbound, you are likely to encounter subtle,
confusing problems in the future. Only attach information to
identifiers with bindings.

Ryan

--- Eli Barzilay <eli at barzilay.org> wrote:

> There is a problem with this approach though: because the
> information
> is kept in a table that is in the syntax environment, it is not
> kept
> around for multiple modules.  For example, this code (due to
> Matthew):
> 
>   (module p mzscheme
>     (require m)
>     (f one +))
>  
>   (module q mzscheme
>     (require m p)
>     (display (g one)))
> 
> wouldn't work.  There is another solution though -- you put the
> syntax-level information as the value of a binding.  The thing is
> that
> a transformer is just a procedure, so you can make a new struct
> type
> that can be applied as a procedure, but also carries extra
> information
> which is accessible for the transformer environment.  So, to make a
> `foo' binding that has a runtime value and a syntax-time value, you
> produce syntax that defines some `hidden-foo' as the actual
> run-time
> expression, then create a macro procedure that always expands to
> `hidden-foo', wrap that in the applicable struct type and hang the
> syntactic information there.
> 
> I used this approach in my course plugin (if you want to see the
> code,
> get it from csu660.barzilay.org/csu660.plt), which is a good sample
> of
> what you can do with this:
> 
>   (define-type EXPR
>     [Num (n number?)]
>     [Add (l EXPR?) (r EXPR?)]
>     [Sub (l EXPR?) (r EXPR?)])
>   (define (eval expr)
>     (cases expr
>       [(Num n) n]
>       [(Add l r) (+ (eval l) (eval r))]))
> 
> Note that unlike the EoPL language, `cases' does not require the
> type
> identifier.  Instead, the `define-type' defines `Num' and `Add' as
> above -- they can be used as a runtime value (constructor
> procedures),
> and they can be used at syntax-time which is how `cases' knows what
> type is expected there (so it can complain about a missing case).
> 
> [I think that Dave Herman came up with the same idea and put it in
> a
> planet library.]
> 
> 
> On Oct 29, Sam Tobin-Hochstadt wrote:
> > What you probably want here is a `module-identifier-mapping',
> which is a
> > hashtable keyed on identifiers, with the equality being
> > `module-identifier=?'.  The transformer for `f' can add `id' to
> the map,
> > and then `g' can recognize id by looking it up in the map.  
> > 
> > The following code demonstrates the idea:
> > 
> > (module m mzscheme
> >   (require-for-syntax (lib "boundmap.ss" "syntax"))
> >   (define-for-syntax mapping (make-module-identifier-mapping))
> >   
> >   (provide f g)
> >   
> >   (define-syntax (f stx)
> >     (syntax-case stx ()
> >       [(f id args ...)
> >        (begin
> >          (module-identifier-mapping-put! mapping #'id #'(args
> ...))
> >          #'(void))]))
> >   
> >   (define-syntax (g stx)
> >     (syntax-case stx ()
> >       [(g id)
> >        (module-identifier-mapping-get mapping #'id)])))
> > 
> > (module n mzscheme
> >   (require m)
> >   
> >   (f one + 3 4)
> >   (f two * 9 10)
> >   
> >   (display (g one))
> >   (newline)
> >   (display (g two)))
> 
> -- 
>           ((lambda (x) (x x)) (lambda (x) (x x)))          Eli
> Barzilay:
>                   http://www.barzilay.org/                 Maze is
> Life!
> _________________________________________________
>   For list-related administrative tasks:
>   http://list.cs.brown.edu/mailman/listinfo/plt-scheme
> 



Posted on the users mailing list.