Dear Racket list,<br><br>As a non-expert of Scheme macro system,<br>I've been struggling for a while to find out how to <br>define a macro that can take a procedure as argument.<br><br>For example, suppose you want to define a macro that takes<br>
an identifier and an expression, modifies the identifier in some <br>
way, and defines this new identifier with the expression.<br>
And suppose you want the identifier modification to be generic,<br>
i.e. the macro should take as argument a procedure that tells<br>
how to build the new identifier.<br>
Here is a way to do it (see below).<br>
<br>Maybe this is a well known technique, maybe there is a <br>better way to do it, or maybe this is straightforward to experts ;<br>but I couldn't find it on Google or in different books, and since<br>that took me some time I thought this could help others.<br>
Any comment is welcome (some of my own comments might<br>even be wrong).<br><br><br>#lang racket<br><br>#|<br>This tutorial shows how to create a generic variable definer,<br>that can take a procedure as argument to build the id name.<br>
|#<br><br>;; First, we create a simple macro that modifies the identifier name.<br>;;<br>;; (datum->syntax #'var new-id-expr ....) makes the new-id visible<br>;; in the same context where the id held by var is used (here, <br>
;; at the call to define-me).<br>;; See datum->syntax for more information.<br>(define-syntax (define-me stx)<br> (syntax-case stx ()<br> [(_ var val)<br> (with-syntax ([id (datum->syntax <br> #'var<br>
(string->symbol<br> (string-append <br> (symbol->string (syntax->datum #'var))<br> "-me"))<br> #'var #'var #'var)])<br>
#'(define id val))]))<br><br>(define-me a 1)<br>a-me<br><br>;; Second, we would like the building of the new id to be modifiable,<br>;; without having to rewrite the entire macro.<br>;; We would like to take another argument to define-me,<br>
;; but this is not possible in a straightforward way,<br>;; because such arguments can only be syntax objects, and not generic code.<br>;;<br>;; So, instead we create a macro maker to abstract the relevant part,<br>;; and call that macro maker to instantiate the desired specific macro.<br>
;; Then we can use that new macro to define the variable.<br>(define-syntax-rule (define-define-me define-me id-maker)<br> (define-syntax (define-me stx)<br> (syntax-case stx ()<br> [(_ var val)<br> (with-syntax ([id (datum->syntax <br>
#'var<br> (string->symbol<br> (id-maker<br> (symbol->string (syntax->datum #'var))<br> ))<br>
#'var #'var #'var)])<br> #'(define id val))])))<br><br>; Instantiate a macro for a specific id-maker.<br>(define-define-me define-me2 (ë(id-str)(string-append id-str "-me2")))<br>
<br>(define-me2 a 2)<br>a-me2<br><br>;; Third, we incorporate the macro call to define-me2 into the macro maker.<br>;; <br>;; The intermediate macro, however defined at the top level,<br>;; is only "temporary", because its name (define-me) is<br>
;; completely internal to the surrounding macro, thus hygiene hides it.<br>;; This also means that the surrounding macro can be used several<br>;; times without interfering with itself.<br>;;<br>;; Note that inside define-me, var must be taken from the call<br>
;; to define-me, and not directly from define-me-proc arguments,<br>;; so that datum->syntax can create the new id for runtime,<br>;; on the contrary to id-maker which is used at compile time.<br>(define-syntax-rule (define-me-proc id-maker var val)<br>
(begin<br> (define-syntax (define-me stx)<br> (syntax-case stx ()<br> [(_ var val)<br> (with-syntax ([id (datum->syntax <br> #'var<br> (string->symbol<br>
(id-maker<br> (symbol->string (syntax->datum #'var))<br> ))<br> #'var #'var #'var)])<br>
#'(define id val))]))<br> (define-me var val)<br> ))<br> <br> <br>(define-me-proc (ë(id-str)(string-append id-str "-me3"))<br> a 3)<br><br>a-me3<br><br>; We can also define a helper, as long as it is defined for-syntax,<br>
; since it is called inside the define-me internal macro, <br>; which is at syntax-level.<br>(define-for-syntax (id-maker id-str)<br> (string-append id-str "-me3-also"))<br><br>(define-me-proc id-maker<br> a '3-also)<br>
<br>a-me3-also<br><br><br>-- <br>Laurent<br>