[racket] [Tutorial?] How to make a macro that takes a procedure as argument

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Thu Sep 16 13:08:35 EDT 2010

Laurent wrote:
> Dear Racket list,
> 
> As a non-expert of Scheme macro system,
> I've been struggling for a while to find out how to
> define a macro that can take a procedure as argument.
> 
> For example, suppose you want to define a macro that takes
> an identifier and an expression, modifies the identifier in some
> way, and defines this new identifier with the expression.
> And suppose you want the identifier modification to be generic,
> i.e. the macro should take as argument a procedure that tells
> how to build the new identifier.
> Here is a way to do it (see below).
> 
> Maybe this is a well known technique, maybe there is a
> better way to do it, or maybe this is straightforward to experts ;
> but I couldn't find it on Google or in different books, and since
> that took me some time I thought this could help others.
> Any comment is welcome (some of my own comments might
> even be wrong).
> 
> 
> #lang racket
> 
> #|
> This tutorial shows how to create a generic variable definer,
> that can take a procedure as argument to build the id name.
> |#
> 
> ;; First, we create a simple macro that modifies the identifier name.
> ;;
> ;; (datum->syntax #'var new-id-expr ....) makes the new-id visible
> ;; in the same context where the id held by var is used (here,
> ;; at the call to define-me).
> ;; See datum->syntax for more information.
> (define-syntax (define-me stx)
>   (syntax-case stx ()
>     [(_ var val)
>      (with-syntax ([id (datum->syntax
>                         #'var
>                         (string->symbol
>                          (string-append
>                           (symbol->string (syntax->datum #'var))
>                           "-me"))
>                         #'var #'var #'var)])
>        #'(define id val))]))
> 
> (define-me a 1)
> a-me
> 
> ;; Second, we would like the building of the new id to be modifiable,
> ;; without having to rewrite the entire macro.
> ;; We would like to take another argument to define-me,
> ;; but this is not possible in a straightforward way,
> ;; because such arguments can only be syntax objects, and not generic code.

It's true that macro arguments are syntax objects, and the rest of your 
tutorial shows one way of interpreting a syntax object as an "expression 
at compile time" (as opposed to "expression at run time", which is what 
most macro arguments are).

It's also possible to do the evaluation immediately, instead of 
trampolining through define-syntax. See syntax-local-eval from 
unstable/syntax. It creates an internal definition context, uses 
syntax-local-bind-syntaxes to evaluate and bind the expression to a 
temporary name, and syntax-local-value to extract the value.

Ryan


> ;; So, instead we create a macro maker to abstract the relevant part,
> ;; and call that macro maker to instantiate the desired specific macro.
> ;; Then we can use that new macro to define the variable.
> (define-syntax-rule (define-define-me define-me id-maker)
>   (define-syntax (define-me stx)
>     (syntax-case stx ()
>       [(_ var val)
>        (with-syntax ([id (datum->syntax
>                           #'var
>                           (string->symbol
>                            (id-maker
>                             (symbol->string (syntax->datum #'var))
>                             ))
>                           #'var #'var #'var)])
>          #'(define id val))])))
> 
> ; Instantiate a macro for a specific id-maker.
> (define-define-me define-me2 (λ(id-str)(string-append id-str "-me2")))
> 
> (define-me2 a 2)
> a-me2
> 
> ;; Third, we incorporate the macro call to define-me2 into the macro maker.
> ;;
> ;; The intermediate macro, however defined at the top level,
> ;; is only "temporary", because its name (define-me) is
> ;; completely internal to the surrounding macro, thus hygiene hides it.
> ;; This also means that the surrounding macro can be used several
> ;; times without interfering with itself.
> ;;
> ;; Note that inside define-me, var must be taken from the call
> ;; to define-me, and not directly from define-me-proc arguments,
> ;; so that datum->syntax can create the new id for runtime,
> ;; on the contrary to id-maker which is used at compile time.
> (define-syntax-rule (define-me-proc id-maker var val)
>   (begin
>     (define-syntax (define-me stx)
>       (syntax-case stx ()
>         [(_ var val)
>          (with-syntax ([id (datum->syntax
>                             #'var
>                             (string->symbol
>                              (id-maker
>                               (symbol->string (syntax->datum #'var))
>                               ))
>                             #'var #'var #'var)])
>            #'(define id val))]))
>     (define-me var val)
>     ))
> 
> 
> (define-me-proc (λ(id-str)(string-append id-str "-me3"))
>   a 3)
> 
> a-me3
> 
> ; We can also define a helper, as long as it is defined for-syntax,
> ; since it is called inside the define-me internal macro,
> ; which is at syntax-level.
> (define-for-syntax (id-maker id-str)
>   (string-append id-str "-me3-also"))
> 
> (define-me-proc id-maker
>   a '3-also)
> 
> a-me3-also



Posted on the users mailing list.