从Racket宏中取消引用

时间:2016-10-02 17:47:27

标签: macros racket quote

我有一个函数可以对表达式进行一些处理:

(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]))

此功能按预期工作:

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

现在我正在尝试创建一个宏,它接受它给出的表达式并用foo-fn的结果替换它。我已经设法通过

获得了一些方法
(define-syntax-rule (foo ex)
  (foo-fn (quote ex)))

但是,这个宏仍然给出了引用的表达式,我希望表达式本身。也就是说,虽然我当前的代码提供了

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

我希望结果是

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

我已经设法通过使用eval找到了解决方法,但我很确定这不是如何使用宏(如果我错了,请纠正我)

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

虽然上述方法有效,但我认为这是一种使用宏的错误方法。什么是首选方法?

1 个答案:

答案 0 :(得分:4)

此处的根本问题不在foo宏中,而在foo-fn中。 foo-fn对基准进行操作,而不是语法对象,因此它会丢弃所有词汇上下文和源位置信息。这是一个不可逆转的操作,所以没有办法从引用的表达式“返回”到语法片段。

您可能希望foo-fn宏执行语法解析,而不是使用foo来执行源转换。使用syntax/parse/define,这非常简单:

(require syntax/parse/define)

(struct sym (s) #:transparent)

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

通过将其作为宏实现,您可以进行语法到语法转换,而不是基准数据转换,从而正确保留词汇上下文。 (这种保存是“宏观卫生”这一概念的组成部分。)

现在,您可以使用foo宏并获得您期望的结果:

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