简体   繁体   中英

Unquoting from a Racket macro

I have a function that does some processing on an expression:

(struct sym (s) #:transparent)

(define (foo-fn ex)
  (match ex
    [(? symbol? s) `(sym (quote ,s))]
    [(? list? xs) (cons (car xs) (map foo-fn (cdr xs)))]
    [x x]))

This function works as expected:

> (foo-fn '(cons x y))
'(cons (sym 'x) (sym 'y))

Now I am trying to make a macro that takes the expression it's given and replaces it with the result of foo-fn . I have managed to get some of the ways there with

(define-syntax-rule (foo ex)
  (foo-fn (quote ex)))

However, this macro still gives the quoted expression, and I would like the expression itself. That is, while my current code gives

> (foo (cons x y))
'(cons (sym 'x) (sym 'y))

I would prefer the result be

> (foo (cons x y))
(cons (sym 'x) (sym 'y))

I have managed to find a workaround through using eval , but I'm pretty sure this is not how macros are meant to be used (correct me if I'm wrong)

(define-syntax-rule (foo ex)
  (eval (foo-fn (quote ex))))

While the above works, it is my belief that it is an incorrect way to use macros. What is the preferred approach?

The root issue here is not in your foo macro, but in foo-fn . The foo-fn operates on datums, not syntax objects, so it throws away all lexical context and source location information. This is an irreversible operation, so there's no way to “go back” from a quoted expression to pieces of syntax.

Instead of using foo-fn to perform your source transformations, it looks like you probably want the foo macro to do the syntax parsing itself. Using syntax/parse/define , this is quite straightforward:

(require syntax/parse/define)

(struct sym (s) #:transparent)

(define-syntax-parser foo
  [(_ s:id) #'(sym 's)]
  [(_ (f x ...)) #'(f (foo x) ...)]
  [(_ x) #'x])

By implementing this as a macro, you have a syntax-to-syntax transformation instead of a datum-to-datum one, which properly preserves lexical context. (This preservation is integral to the concept known as “macro hygiene”.)

Now, you can use the foo macro and get the result you expect:

> (foo (cons x y))
(cons (sym 'x) (sym 'y))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM