Eval-何时使用?

时间:2012-05-20 15:17:32

标签: compilation macros lisp common-lisp eval-when

在阅读了很多关于Lisp eval-when运算符的文档后,我仍然无法理解它的用法,我知道使用这个运算符我可以控制表达式的评估时间,但我无法弄清楚任何一个例子这可能适用吗?

最诚挚的问候, utxeee。

1 个答案:

答案 0 :(得分:24)

汇编Lisp文件

以编译Lisp文件为例。 Lisp编译器处理顶级表单。这些可以是任意的Lisp格式,DEFUN,DEFMACROS,DEFCLASS,函数调用,......

文件编译器工作原理的全部内容过于复杂,无法在此解释,但有一些事情:

  • 文件编译器为(DEFUN foo () )表单生成代码。但它没有执行defun形式。因此,在编译期间,已知存在函数FOO,但在编译期间'FOO'的代码不可用。编译器为编译的文件生成代码,但不将其保留在内存中。你不能在编译时调用这样的函数。

  • 对于宏,它的工作方式略有不同:(DEFMACRO BAZ ...)。文件编译器不仅会编译宏并注意它在那里,但它也会在编译时使宏可用。它被加载到编译器 environment

因此想象一下文件中的表单序列:

(defmacro baz ...)

(defun foo () (baz ...))

这是有效的,因为文件编译器知道宏BAZ,当它编译FOO的代码时,它可以扩展宏窗体。

现在让我们看一下以下示例:

(defun bar (form) ...)

(defmacro baz (form) (bar form))

(defun foo () (baz ...))

以上不起作用。现在,宏BAZ通过调用它来使用函数BAR。当编译器尝试编译函数FOO时,它无法展开BAZ宏,因为BAR无法调用,因为BAR的代码不是加载到编译时环境中。

有两种解决方案:

  1. 使用单独的文件先编译加载BAR
  2. 使用EVAL-WHEN
  3. EVAL-WHEN的示例:

     (eval-when (:compile-toplevel :execute :load-toplevel)
       (defun bar (form) ...)
     )
    
     (defmacro baz (form) (bar form))
    
     (defun foo () (baz ...))
    

    现在EVAL-WHEN指示文件编译器在编译期间实际运行DEFUN表单。这样做的结果是:文件编译器现在知道编译时的BAR的定义。因此,当文件编译器需要在使用BAR的宏扩展期间调用BAZ时,它可用。

    在编译文件后不需要该函数时,只能使用:compile-toplevel。如果以后使用它,那么我们需要确保它被加载。

    所以EVAL-WHEN允许指定是否应该运行特定的代码

    • 在编译文件期间
    • 在加载文件期间
    • 执行期间
    用户代码中经常不使用

    EVAL-WHEN。如果你使用它,那么你应该问问自己是否真的需要它。