你如何在Lisp编译器中编译宏? [英] How do you compile macros in a Lisp compiler?

查看:177
本文介绍了你如何在Lisp编译器中编译宏?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Lisp解释器中,可以轻松地在 eval 中创建一个分支,以扩展宏,并且在扩展宏的过程中,调用函数来构建扩展表达。我在使用低级宏之前就这样做了,很容易实现。



但是,在编译器中没有任何函数可以调用来构建扩展代码:在下面的例子中可以很容易地看到这个问题:

 (defmacro cube(n)
(x(gensym)))
`(let((,x,n))
(*,x,x,x)))
/ pre>

当宏由解释器扩展时,它调用 gensym ,并执行您期望的操作。当由编译器扩展时,您将生成一个 let 的代码,它将 x 绑定到(gensym),但是只有在 编译器才能做正确的事情时才需要gensymmed符号。因为 gensym 在编译宏之前并没有被真正调用,所以并不是非常有用。



更奇怪的是,当一个宏构建一个列表,用于使用 map 过滤器 p>

那么这是如何工作的?当然编译的代码不会被编译为(eval * macro-code *),因为这将是非常低效的。是否有一个良好的Lisp编译器,这是清楚的?

解决方案

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



Lisp。



EVAL 不是解释器。 EVAL可以用编译器实现。一些Common Lisp实现甚至没有解释器。然后 EVAL 是调用编译器编译代码,然后调用编译的代码。这些实现使用增量编译器,它可以编译诸如 2 (+ 2 3),<$



宏展开是使用函数 MACROEXPAND MACROEXPAND-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;名称将是CUBE
(let((x(gensym)))
`(let((,x,n))
(*,x,x,x))))

(宏函数'my-cube)#'cube-internal)
/ pre>

在实际的CL实现中, DEFMACRO 扩展不同,不使用 CUBE-INTERNAL 。但在概念上,它定义了一个宏函数并注册它。



当Lisp编译器看到宏定义时,它通常编译宏函数并将其存储在当前so-称为环境。如果环境是运行时环境,则它在运行时被记住。如果环境是编译文件时的编译环境,则在编译文件后会忘记宏。编译的文件需要加载,以便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'c​​ube)
CUBE
NIL
NIL

code> 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中有必要在编译器使用之前定义一个宏。


In a Lisp interpreter, there can easily be a branch in eval that can expand a macro, and in the process of expanding it, call functions to build up the expanded expression. I've done this before using low-level macros, it's easily concieved.

But, in a compiler there aren't any functions to call to build up the expanded code: The issue can be seen quite simply in the following example:

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

When the macro is expanded by an interpreter, it calls gensym and does what you expect. When expanded by a compiler, you'd generate the code for a let which binds x to (gensym) but the gensymmed symbol is only necessary for the compiler to do the right thing. And since gensym isn't actually called before the macro is compiled, it's not very useful.

This gets even more strange to me when a macro builds up a list to be used as the expansion using map or filter.

So how does this work? Surely the compiled code isn't compiled to (eval *macro-code*) because that'd be horribly inefficient. Is there a well written Lisp compiler where this is clear?

解决方案

How this works is very different in various Lisp dialects. For Common Lisp it is standardized in the ANSI Common Lisp standard and the various Common Lisp implementations differ mostly whether they use a compiler, an interpreter or both.

The following assumes Common Lisp.

EVAL is not the interpreter. EVAL can be implemented with a compiler. Some Common Lisp implementations even don't have an interpreter. Then EVAL is a call to the compiler to compile the code and then calls the compiled code. These implementations use an incremental compiler, which can compile also simple expressions like 2, (+ 2 3), (gensym), and so on.

Macroexpansion is done with the functions MACROEXPANDand MACROEXPAND-1.

A macro in Common Lisp is a function that expects some forms and returns another form. DEFMACRO registers this function as a macro.

Your macro

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

is nothing but a Lisp function, which is registered as a macro.

The effect is similar to this:

(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)

In a real CL implementation DEFMACRO expands differently and does not use a name like CUBE-INTERNAL. But conceptually it is defining a macro function and registering it.

When the Lisp compiler sees a macro definition, it usually compiles the macro function and stores it in the current so-called environment. If the environment is the runtime environment, it is remembered at runtime. If the environment is the compiler environment while compiling a file, the macro is forgotten after the file is compiled. The compiled file needs to be loaded so that Lisp then knows the macro.

So, there is a side effect in defining a macro and compiling it. The compiler remembers the compiled macro and stores its code.

When the compiler now sees some code which uses the macro (cube 10), then the compiler just calls the macro function which is stored in the current environment under the name CUBE, calls this macro function which 10 as an argument, and then compiles the generated form. As mentioned above, it is not done directly, but via the MACROEXPAND functions.

Here is the Macro definition:

CL-USER 5 > (defmacro cube (n)
              (let ((x (gensym)))
                `(let ((,x ,n))
                   (* ,x ,x ,x))))
CUBE

We compile the macro:

CL-USER 6 > (compile 'cube)
CUBE
NIL
NIL

MACRO-FUNCTION returns the function for a macro. We can call it like any other function with FUNCALL. It expects two arguments: a whole form like (cube 10) and an environment (here NIL).

CL-USER 7 > (funcall (macro-function 'cube) '(cube 10) nil)
(LET ((#:G2251 10)) (* #:G2251 #:G2251 #:G2251))

It is also possible to take a function (which accepts two arguments: a form and an environment) and store it using SETF as a macro function.

Summary

When the Common Lisp compiler runs, it simply knows the macro functions and calls them when necessary to expand code via the built-in macro expander. The macro functions are simply Lisp code themselves. When the Lisp compiler sees a macro definition, it compiles the macro function, stores it in the current environment and uses it to expand subsequent uses of the macro.

Note: This makes it necessary in Common Lisp that a macro is defined before it can be used by the compiler.

这篇关于你如何在Lisp编译器中编译宏?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆