[英]How does macroexpansion actually work in Lisp?
我想更详细地解释宏扩展是如何工作的,至少在 Emacs Lisp 中,但对其他 Lisp 的概述将不胜感激。 我通常看到它解释的方式是宏的参数未经评估地传递给主体,然后执行并返回一个新的 LISP 形式。 但是,如果我这样做
(defun check-one (arg)
(eq arg 1))
(defmacro check-foo (checker foo)
(if (checker 1)
`(,foo "yes")
`(,foo "no")))
我希望
(check-foo check-one print)
首先扩展到
(if (check-one 1)
`(print "yes")
`(print "no))
然后最后到(print "yes")
,但我得到一个“检查器”功能无效错误。 另一方面,如果我已经定义
(defmacro check-foo (checker foo)
(if (funcall checker 1)
`(,foo "yes")
`(,foo "no")))
那么我会有预期的行为。 所以表达式确实在未评估的主体中被替换,但由于某种原因函数不起作用? 宏扩展时解释器遵循的分步过程是什么? 有没有一本很好的教科书可以严格解释这一点?
考虑宏的一个好方法是它们只是函数,就像任何其他函数一样。
但它们是函数,其参数是源代码,其值也是源代码。
在 elisp 中,作为函数的宏并不十分明确:我认为,一些较低级别的功能并未公开。 但是在 Common Lisp 中,这就是宏的实现方式:宏有一个关联的函数,调用这个函数来扩展宏,它的值是新的源代码。 例如,如果你有心,你可以像这样在 Common Lisp 中编写宏。
(defun expand-fn (form environment)
;; not talking about environment
(declare (ignore environment))
(let ((name (second form))
(arglist (third form))
(body (cdddr form)))
`(function (lambda ,arglist
(block ,name
,@body)))))
(setf (macro-function 'fn) #'expand-fn)
现在fn
是一个宏,它将构造一个“知道它的名字”的函数,所以你可以写
(fn foo (x) ... (return-from foo x) ...)
这变成
(function (lambda (x) (block foo ... (return-from foo x))))
在 Common Lisp 中, defmacro
本身就是一个宏,它安排安装一个合适的宏功能,并处理使宏在编译时可用 &c。
在 elisp 中,看起来这个低层似乎没有由语言指定,但我认为假设事情以相同的方式工作是安全的。
那么宏的工作就是获取一堆源代码并从中计算出另一堆源代码,这是宏的扩展。 当然,真正巧妙的技巧是,由于源代码(参数和值)都表示为 s 表达式,Lisp 是一种用于处理 s 表达式的出色语言,您可以在 Lisp 本身中编写宏。
这里有很多复杂的极端情况,例如本地宏等。 但是,这几乎是它的工作原理。
从某种形式<f>
开始:
<f>
是(<a> ...)
其中<a>
是一个符号,请检查<a>
的宏函数。 如果它有一个,则在整个表单上调用它,并调用它返回的值<f'>
:现在只需递归<f'>
。<f>
是(<a> ...)
其中<a>
是一个符号,它命名一个特殊运算符(类似于if
)然后递归它的规则说必须是宏扩展的特殊运算符的子形式。 例如,在(if <x> <y> <z>)
形式中,所有<x>
、 <y>
和<z>
都需要进行宏扩展,而在(setq <a> <b>)
, 只有<b>
会受到宏扩展,等等:这些规则是硬连线的,这就是特殊运算符特别的原因。<f>
是(<a> ...)
其中<a>
是不是上述两种情况的符号,那么它是一个函数调用,并且表单主体中的表单被宏扩展,就是这样。<f>
是((lambda (...) ...) ...)
则lambda
主体中的形式(但不是它的参数!)被宏扩展,然后大小写与最后一个相同.<f>
可能不是复合形式:这里没什么可做的。我想这就是所有情况。 这不是对该过程的完整描述,因为存在局部宏等复杂情况。 但我认为这已经足够了。
请注意,宏扩展发生在“外在”:像(a ...)
这样的形式被扩展,直到你得到不是宏形式的东西,然后才可能扩展正文。 这是因为,在宏完全展开之前,您不知道哪些子窗体(如果有的话)甚至有资格进行宏展开。
我的猜测是你想要发生的是(check-foo bog foo)
应该变成(if (bog 1) (foo yes) (foo no))
。 所以得到这个的方法就是这个形式是宏函数需要返回的。 我们可以使用 CL 低级工具来编写:
(defun check-foo-expander (form environment)
;; form is like (check-foo pred-name function-name)
(declare (ignore environment)) ;still not talking about environment
`(if (,(second form) 1)
(,(third form) "yes")
(,(third form) "no")))
我们可以检查:
> (check-foo-expander '(check-foo bog foo) nil)
(if (bog 1) (foo "yes") (foo "no"))
然后将其安装为宏:
> (setf (macro-function 'check-foo) #'check-foo-expander)
现在
> (check-foo evenp print)
"no"
"no"
> (check-foo oddp print)
"yes"
"yes"
但是使用defmacro
编写它更容易:
(defmacro check-foo (predicate function)
`(if (,predicate 1)
(,function "yes")
(,function "no")))
这是同一件事(或多或少),但更容易阅读。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.