[racket] Macro that Generate Names

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Thu Sep 23 14:01:38 EDT 2010

On 09/23/2010 08:24 AM, Doug Williams wrote:
> Consider the following macro that has a definitional macro, define-x,
> that stores a function definition in a structure bound to a generated
> name, and a macro that either calls that stored function or it does
> something else. [I used a generated name, x:<name>, because I need to
> use the original for another purpose - illustrated here by just calling
> the stored function.]
>
> #lang racket
>
> (struct x-info (name func))
>
> (define-syntax (define-x stx)
>    (syntax-case stx ()
>      ((define-x (name . parameters) . body)
>       (with-syntax ((id (datum->syntax
>                          (syntax define-x)
>                          (string->symbol
>                           (format "x:~a" (syntax->datum #'name))))))
>         #'(define-values (id name)
>             (let ((func (lambda parameters . body)))
>               (values (x-info 'name func) func)))))))
>
> (define-syntax (do-x stx)
>    (syntax-case stx ()
>      ((do-x (name . arguments))
>       (with-syntax ((id (datum->syntax
>                          (syntax define-x)
>                          (string->symbol
>                           (format "x:~a" (syntax->datum #'name))))))
>         (if (identifier-binding #'id)
>             #'((x-info-func id) . arguments)
>             #'(name . arguments))))))
>
> (provide (all-defined-out))
>
> Now, I reference this in a separate module:
>
> #lang racket
>
> (require "x-2.ss")
>
> (define (dummy a b c)
>    (printf "dummy: ~a ~a ~a~n" a b c))
>
> (define-x (my-x a b c)
>    (printf "my-x: ~a ~a ~a~n" a b c))
>
> (do-x (my-x 1 2 3))
> (my-x 1 2 3)
> (do-x (dummy 1 2 3))
>
> And, when I run it I get the desired results.
>
> my-x: 1 2 3
> my-x: 1 2 3
> dummy: 1 2 3
>
> Now, the question is whether this is the 'right' way to do this and are
> there any 'gotchas' that I'm not aware of? Is there something better to
> use than relying on identifier-binding to return #f when there is no
> binding? And, whatever obvious questions I don't even know enough to ask.

One thing I'd worry about is someone providing <name> from a module 
without providing x:<name>. A solution to this problem is to have a 
single public name and change its meaning depending on the context.

One way to do that is to make x-info be an applicable struct:

   (struct x-info (name func)
     #:property prop:procedure
                (lambda (x . args)
                  (apply (x-info-func x) args)))

Then if you define my-x as an x-info, the struct itself handles this case:

   (my-x 1 2 3)

and do-x can behave differently when given something that (dynamically) 
answers true to x-info?.

Using identifier-binding is fragile in this case. In particular, even if 
a binding exists, it doesn't guarantee that it's an x-info value. A 
better way to encode the idea "my-x was defined using define-x" is to 
bind my-x to compile-time information using define-syntax. That's how 
structs work, for example: the struct name is bound to a record 
describing the struct, but the record also acts as a macro transformer 
to handle the syntax of constructor application.

   (begin-for-syntax
     (struct x-info (name func-id)
       #:property prop:procedure
                  (lambda (x stx)
                    (syntax-case stx ()
                      [(_ arg ...)
                       (with-syntax ([func-id (x-info-func-id x)])
                         #'(func-id arg ...))]))))

Now define-x can use a hygiene-generated name for the procedure, since 
it will only be accessed by going through the x-info name.

   (define-x (my-x x y z) ___)
   =>
   (begin (define (func x y z) ___)
          (define-syntax my-x
            (x-info 'my-x (quote-syntax func))))

Finally, do-x uses syntax-local-value to see whether my-x is bound to 
compile-time information and, if so, whether that information is 
specifically an x-info.

   (define-syntax (do-x stx)
    (syntax-case stx ()
      [(do-x (name . arguments))
       (let ([v (syntax-local-value #'name (lambda () #f))])
         (if (x-info? v) ___ ___))]))

> In the actually application, I am using syntax-parse to parse a rather
> more complicated syntax. Is there any way in syntax-parse I can define a
> syntax class that would recognize a class of identifier based on their
> bindings?

If you use the compile-time information approach I sketched above, yes. 
The 'static' syntax class recognizes identifiers statically bound to 
compile-time information matching a predicate that you supply.

There's an example of 'static' in the implementation of 
unstable/class-iop. See unstable/private/class-iop-ct first for the 
struct definition and syntax class definition ('static-interface'). Then 
look at 'define-interface' and 'send:' in unstable/class-iop for how 
static interfaces are defined and used.

Ryan


Posted on the users mailing list.