在宏中使用外部(词法)环境中的变量

时间:2014-08-29 21:38:46

标签: macros scheme lisp common-lisp

如何让这块宏按预期运行? - 我想从词汇环境中捕获p,而不必将其作为参数发送给宏。

(define-syntax-rule (fi a b)
    (if p a b)) ;--->capture `p` from lexical env

(let ((p #t))
    (fi 1 2))

奖金谢谢 - 我如何在CL中做同样的事情?

2 个答案:

答案 0 :(得分:2)

在Common Lisp中,一个宏只是一个函数,它将代码的列表结构作为输入,并返回表示新代码的列表结构。

(defmacro fi (a b)
  `(if p ,a ,b))

所以,如果你像这样使用fi:

(let ((p t)) ; Common Lisp uses 't' for truth.
   (fi 1 2))

就好像你输入了一样:

(let ((p t))
  (if p 1 2))

要想看看如何进行这种扩展,想象fi是一个函数,你给它的参数为1和2。

(fi 1 2) => (if p 1 2)

然后获取它返回的列表结构,并用对fi的调用替换它。

您给出的示例很简单,因为参数可以自我评估。如果你有像表达式(* 1 1)和(+ 1 1)那样更复杂的东西,则传入实际的列表结构(a的值是列表(* 1 1),b的值是列表(+ 1 1))

(fi (* 1 1) (+ 1 1)) => (if p (* 1 1) (+ 1 1))

答案 1 :(得分:1)

您无法使用syntax-rules捕获本地绑定。不过,您可以使用syntax-case

(define-syntax fi
  (lambda (stx)
    (syntax-case stx ()
      ((_ a b)
       (with-syntax ((p (datum->syntax stx #'p)))
         #'(if p a b))))))

但是,使用datum->syntax来捕获像这样的固定名称的标识符并不理想。如果你正在使用Racket,最好使用语法参数。


对于没有syntax-case但显式重命名的Scheme实现,您可以这样编写宏:

(define-syntax fi
  (er-macro-transformer
    (lambda (exp rename compare)
      `(,(rename 'if) p ,(cadr exp) ,(caddr exp)))))

有些人发现它更简单,但是您有责任重命名您未有意捕获的所有内容。在这种情况下,我们会明确重命名if;对于使用lambdalet等的大多数其他宏,必须重命名这些宏。