[racket] Need help with macro...

From: Brian Mastenbrook (brian at mastenbrook.net)
Date: Mon Feb 13 18:44:34 EST 2012

On 02/12/2012 12:30 PM, FS Racket wrote:
> I'm trying to write a macro that where the pre-transformation form is:
>
> (==> exp1 exp2 ... expn)
>
> and where exp2 through expression expn are sexps that contain a '_'
> placeholder.  The idea is that starting with exp2, the _ gets replaced
> with the previous expression.  For example:
>
> (==> 12 (+ _ 2) (* 3 _))  would become  (* 3 (+ 12 2)).

The problem with this kind of macro is that it can lead to all sorts of 
strange composition issues. Let's take one simple version of it that 
works for all of the cases you gave, and see where it breaks down:

(define-syntax ==>
   (lambda (stx)
     (letrec ((find-_
               (lambda (stx)
                 (syntax-case stx (_)
                   (id (and (identifier? #'id)
                            (free-identifier=? #'id #'_)) #'id)
                   ((a ...) (ormap find-_ (syntax->list #'(a ...))))
                   (#(a ...) (ormap find-_ (syntax->list #'(a ...))))
                   (id #f)))))
       (syntax-case stx ()
         ((==> e1 e2) (find-_ #'e2)
                      (with-syntax ((it (find-_ #'e2)))
                        #'(let ((it e1)) e2)))
         ((==> e1 e2 e3 e4 ...)
          #'(==> (==> e1 e2) e3 e4 ...))
         ((==> e1) #'e1)))))

In this version of the macro, (let ((_ 1)) (==> 2 _)) returns 1: the 
lexical binding of _ takes precedence. However, this comes at a cost: 
now something like (==> 1 (==> (+ _ 1) (+ _ 2))) doesn't work. Let's try 
again.

(define-syntax ==>
   (lambda (stx)
     (syntax-case stx ()
       ((==> e1 e2)
        (with-syntax ((it (datum->syntax #'e2 '_)))
          #'(let ((it e1)) e2)))
       ((==> e1 e2 e3 e4 ...)
        #'(==> (==> e1 e2) e3 e4 ...))
       ((==> e1) #'e1))))

However, this defies our expectations when ==> is used in a macro:

 > (define-syntax f (syntax-rules () ((f v) (==> 2 v))))
 > (==> 1 (f _))
2

Racket provides a tool called "syntax parameters" which can be used for 
this kind of macro. Let's give that a try:

(require racket/stxparam)
(define-syntax-parameter _ (syntax-rules ()))

(define-syntax ==>
   (lambda (stx)
     (syntax-case stx ()
       ((==> e1 e2)
        #'(let ((v e1))
            (syntax-parameterize ((_ (make-rename-transformer #'v)))
                                 e2)))
       ((==> e1 e2 e3 e4 ...)
        #'(==> (==> e1 e2) e3 e4 ...))
       ((==> e1) #'e1))))

Oh, but we still have the same problem when ==> is used inside the 
definition of a macro:

 > (define-syntax f (syntax-rules () ((f v) (==> 2 v))))
 > (==> 1 (f _))
2

In a later message you tried an approach that simply binds the first 
identifier with name _ that it finds. Let's give that a try:

(define-syntax ==>
   (lambda (stx)
     (letrec ((find-_
               (lambda (stx)
                 (syntax-case stx (_)
                   (id (eq? (syntax->datum #'id) '_) #'id)
                   ((a ...) (ormap find-_ (syntax->list #'(a ...))))
                   (#(a ...) (ormap find-_ (syntax->list #'(a ...))))
                   (id #f)))))
       (syntax-case stx ()
         ((==> e1 e2) (find-_ #'e2)
                      (with-syntax ((it (find-_ #'e2)))
                        #'(let ((it e1)) e2)))
         ((==> e1 e2 e3 e4 ...)
          #'(==> (==> e1 e2) e3 e4 ...))
         ((==> e1) #'e1)))))


 > (let-syntax ((m (syntax-rules () ((_ v) (==> 1 (+ v _)))))) (let ((_ 
2)) (m _)))
. _: wildcard not allowed as an expression in: _

Whoops! How did that happen? Well, it found the wrong identifier with a 
name of _.

The bottom line is that if you want to write macros whose behavior can 
be easily understood, *don't break hygiene*. Write the macro the way 
Matthias suggested. Breaking hygiene will always eventually lead to a 
head-scratching puzzlement when your understanding of how your macro 
works falls down and your unhygienically bindings fail to DWIM.

-- 
Brian Mastenbrook
brian at mastenbrook.net
http://brian.mastenbrook.net/


Posted on the users mailing list.