[racket] beginner question about macros

From: Danny Yoo (dyoo at cs.wpi.edu)
Date: Sun Dec 4 21:09:53 EST 2011

> I currently learning scheme macros, and trying to figure out how to do
> two particulars things:

[code cut]


You may want to look at syntax-case style macros, which let you do
computation at compile time.  When I try to write macros purely via
syntax-rules templates, occasionally I have difficulty.  I usually
have an easier time with macros when I think of them just as regular
functions that take syntax objects and return new syntax objects.


Here's an example of a syntax-case macro that does a silly thing:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#lang racket/base

;; When we do computation at compile-time, we want to
;; use the following libraries:
(require (for-syntax racket/base
                     racket/list))

;; (moo! expr1 expt2 ...)
;;
;; This mooifies the expressions between pairs of statements.
;;
(define-syntax (moo! stx)
  (syntax-case stx ()
    [(_ stmt ...)
     (begin

       ;; In a syntax-case, the bindings are held in the syntactic
       ;; environment.  When we create new syntax objects with
       ;; #'(...), we can get at the individual bindings by naming them.

       ;; Let's extract the statements into a plain old list, whose
       ;; elements are the individual syntax objects.

       ;; my-statements: (listof syntax)
       (define my-statements (syntax->list #'(stmt ...)))

       ;; An we know how to process a list.  Here, I'll inject
       ;; a simple, dignified printf between each statement.
       (define (inject-moo stmts)
         (cond
           [(empty? stmts)
            empty]
           [(empty? (rest stmts))
            stmts]
           [else
            (cons (first stmts)
                  (cons #'(printf "moo!\n")
                        (inject-moo (rest stmts))))]))

       ;; Now let's make that updated list of statements...

       ;; mooed-statements: (listof syntax)
       (define mooed-statements
         (inject-moo my-statements))


       ;; Now all that's left to do is turn this list of syntax objects
       ;; back into a syntax object.
       ;;
       ;; Although we could do it this way,
       ;;
       ;;     (datum->syntax stx `(begin , at mooed-statements))
       ;;
       ;; it's usually better to construct the syntax object without the
       ;; intermediate s-expression.  We can inject our values using
       ;; with-syntax, like this:
       ;;

       (with-syntax ([(new-stmts ...) mooed-statements])
         ;; In the context of with-syntax, the "syntactic" environment
         ;; includes these new bindings.
         ;; The uses of new-stmts in a #'(...) will refer back to the
         ;; mooed-statements.
         ;;
         ;; Here's our final result:
         #'(begin new-stmts ...)))]))


;; Now let's try using it.
(moo! (displayln "hello")
      (displayln "world")
      (displayln "this is a test"))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

The code is more verbose than I'd usually write it.  I'm trying to
break down the steps and give names to all my intermediate values,
though, and hopefully it should be easy to see what it's doing.


You may want to go through "Macros as Compilers" (chapter 36) in
Programming Languages: Application and Interpretation
(http://www.cs.brown.edu/~sk/Publications/Books/ProgLangs/) for more
exercises on doing things with syntax-case macros.


Posted on the users mailing list.