在Lisp解释器中,eval中很容易存在一个可以扩展宏的分支,并且在扩展该宏的过程中,调用函数来构建扩展的表达式。在使用低级宏之前,我已经做到了这一点,这很容易理解。

但是,在编译器中,没有任何函数可以调用来构建扩展的代码:在以下示例中可以很简单地看出问题:

(defmacro cube (n)
    (let ((x (gensym)))
      `(let ((,x ,n))
          (* ,x ,x ,x))))

当宏由解释器扩展时,它将调用gensym并执行您期望的操作。当由编译器扩展时,您将为let生成代码,该代码将x绑定(bind)到(gensym),但是gensymmed符号仅对于编译器执行正确的操作是必需的。而且由于在编译宏之前实际上并未调用gensym,所以它不是很有用。

当宏使用mapfilter建立一个用作扩展的列表时,这对我来说变得更加奇怪。

那么这是如何工作的呢?当然,编译后的代码不会编译为(eval *macro-code*),因为这效率极低。有没有写得很清楚的Lisp编译器?

最佳答案

在各种Lisp方言中,这是如何工作的非常不同。对于Common Lisp,它是在ANSI Common Lisp标准中进行标准化的,并且各种Common Lisp实现都主要不同,无论它们使用的是编译器,解释器还是两者。

以下假设Common Lisp。

EVAL不是解释器。可以使用编译器来实现EVAL。一些Common Lisp实现甚至没有解释器。然后EVAL是对编译器的调用,以编译代码,然后调用已编译的代码。这些实现使用增量编译器,该编译器还可以编译诸如2(+ 2 3)(gensym)等的简单表达式。

宏扩展使用MACROEXPANDMACROEXPAND-1函数完成。

Common Lisp中的宏是一个需要某些形式并返回另一种形式的函数。 DEFMACRO将此功能注册为宏。

你的宏

(defmacro cube (n)
  (let ((x (gensym)))
    `(let ((,x ,n))
        (* ,x ,x ,x))))

只是Lisp函数,它注册为宏。

效果类似于:
(defun cube-internal (form environment)
  (destructuring-bind (name n) form   ; the name would be CUBE
    (let ((x (gensym)))
      `(let ((,x ,n))
         (* ,x ,x ,x)))))

(setf (macro-function 'my-cube) #'cube-internal)

在实际的CL实现中,DEFMACRO的扩展方式不同,并且不使用类似于CUBE-INTERNAL的名称。但是从概念上讲,它是在定义宏函数并进行注册。

Lisp编译器看到宏定义时,通常会编译该宏函数并将其存储在当前所谓的环境中。如果环境是运行时环境,则会在运行时记住该环境。如果环境是编译文件时的编译器环境,则在编译文件后会忘记该宏。需要加载已编译的文件,以便Lisp知道宏。

因此,定义宏并进行编译存在副作用。编译器会记住已编译的宏并存储其代码。

现在,当编译器看到一些使用宏(cube 10)的代码时,编译器仅调用以CUBE的名称存储在当前环境中的宏函数,然后调用以10作为参数的宏函数,然后编译生成的表单。如上所述,它不是直接完成的,而是通过MACROEXPAND函数完成的。

这是宏定义:
CL-USER 5 > (defmacro cube (n)
              (let ((x (gensym)))
                `(let ((,x ,n))
                   (* ,x ,x ,x))))
CUBE

我们编译宏:
CL-USER 6 > (compile 'cube)
CUBE
NIL
NIL
MACRO-FUNCTION返回宏的函数。我们可以像其他函数一样使用FUNCALL来调用它。它需要两个参数:像(cube 10)这样的整体形式和一个环境(这里是NIL)。
CL-USER 7 > (funcall (macro-function 'cube) '(cube 10) nil)
(LET ((#:G2251 10)) (* #:G2251 #:G2251 #:G2251))

也可以采用一个函数(该函数接受两个参数:一个表单和一个环境)并使用SETF作为宏函数来存储它。

摘要

当Common Lisp编译器运行时,它只知道宏函数,并在需要时通过内置宏扩展器扩展代码以调用它们。宏函数本身就是Lisp代码。当Lisp编译器看到宏定义时,它将编译该宏函数,将其存储在当前环境中,并使用它来扩展该宏的后续用法。

注意:这使得在Common Lisp中必须定义一个宏,然后编译器才能使用它。

关于compiler-construction - 您如何在Lisp编译器中编译宏?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/7072980/

10-15 15:54
查看更多