[racket] Need help with macro...
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/