[racket] Macro that Generate Names
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