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

Macros 在宏中使用外部(词汇)环境中的变量,macros,scheme,lisp,common-lisp,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中做同样的事情?您不能用语法规则捕获本地绑定。您可以使用语法大小写,但是: (define-syntax fi (lambda (stx) (syntax-ca

如何使此宏按预期运行?--我想从词法环境中捕获p,而不必将其作为参数发送到宏

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

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

额外的感谢--我如何在CL中做同样的事情?

您不能用
语法规则捕获本地绑定。您可以使用
语法大小写
,但是:

(define-syntax fi
  (lambda (stx)
    (syntax-case stx ()
      ((_ a b)
       (with-syntax ((p (datum->syntax stx #'p)))
         #'(if p a b))))))
然而,使用
datum->syntax
来捕获像这样的固定名称的标识符并不理想。如果您使用的是Racket,最好使用语法参数


对于没有
语法大小写但具有显式重命名的方案实现,可以这样编写宏:

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

有些人觉得这很简单,但是你有责任重新命名你不是有意捕获的东西。在本例中,我们显式地重命名了
if
;对于大多数其他使用lambda
let
等的宏,这些宏都必须重命名。

在公共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的调用

您给出的示例很简单,因为参数的计算结果是独立的。如果有更复杂的表达式(*11)和(+11),则会传入实际的列表结构(a的值是列表(*11),b的值是列表(+1))


问题不是关于Common Lisp,而是Scheme。@Sylvester从“奖金感谢——我如何在CL中做同样的事情?”的问题中回答道,“我的错,没有听清楚。”。在这种情况下,它需要一个标签:)谢谢克里斯!我将对此进行一次尝试,但出于好奇,这里没有任何方法可以实现defmacro那样的简单性吗?@rebnoob大多数Scheme方言都提供define macro,这几乎是一样的。正如您所说,datum->syntax的这种用法是有问题的。如果在宏的词法范围内绑定了相同的外部名称,并且在调用站点本地绑定了相同的外部名称(比如使用let表达式),则它将选择其中的第一个名称。定义宏将做相反的事情,这至少是一个更可预测的破坏卫生!我应该坚持定义宏(如果可用),或者语法参数(如果可用)。@rebnoob在Scheme中使用类似defmacro的东西的问题是,捕获其他东西太容易了。例如,在该宏的特定情况下,如果在当前词汇上下文中重新定义了
,该怎么办?(Common Lisp通过使用符号包来解决这个问题,并确保
Common-Lisp
包中的东西不能被重新定义。Scheme没有这样的概念。)话虽如此,如果您想要“简单性”,另一种方法是编写显式重命名宏。我将用这样一个实现更新我的帖子。@malisper
define macro
在几乎任何提供语法规则以外内容的Scheme系统上都很容易定义。然而,
define macro
的问题在于它没有任何方法来限制卫生的缺乏。即使是GenSym也无法应对这样一个事实,即当前宏中的所有“免费”标识符(如
if
,或仅是
lambda
let
)都可以在当前词汇上下文中反弹。
(fi (* 1 1) (+ 1 1)) => (if p (* 1 1) (+ 1 1))