繁体   English   中英

宏扩展在 Lisp 中实际上是如何工作的?

[英]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>开始:

  1. 如果<f>(<a> ...)其中<a>是一个符号,请检查<a>的宏函数。 如果它有一个,则在整个表单上调用它,并调用它返回的值<f'> :现在只需递归<f'>
  2. 如果<f>(<a> ...)其中<a>是一个符号,它命名一个特殊运算符(类似于if )然后递归它的规则说必须是宏扩展的特殊运算符的子形式。 例如,在(if <x> <y> <z>)形式中,所有<x><y><z>都需要进行宏扩展,而在(setq <a> <b>) , 只有<b>会受到宏扩展,等等:这些规则是硬连线的,这就是特殊运算符特别的原因。
  3. 如果<f>(<a> ...)其中<a>是不是上述两种情况的符号,那么它是一个函数调用,并且表单主体中的表单被宏扩展,就是这样。
  4. 如果<f>((lambda (...) ...) ...)lambda主体中的形式(但不是它的参数!)被宏扩展,然后大小写与最后一个相同.
  5. 最后<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.

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