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

From: Laurent (laurent.orseau at gmail.com)
Date: Thu Sep 16 03:58:09 EDT 2010

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.
;;
;; 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


-- 
Laurent
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20100916/b19db34b/attachment.html>

Posted on the users mailing list.