Eval-when 何时使用?

15

在阅读了大量有关Lisp语言的eval-when运算符的文档后,我仍然无法理解它的用途。我知道使用此运算符可以控制表达式的求值时间,但是我无法想出任何适用的示例?

最好的问候, utxeee。

1个回答

28

Lisp文件的编译

以Lisp文件的编译为例。Lisp编译器处理顶层形式,这些形式可以是任意的Lisp形式,DEFUNs,DEFMACROS,DEFCLASS,函数调用等。

整个文件编译器如何工作的故事在此无法详细解释,但有几点需要注意:

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

  • 对于宏,它的工作方式略有不同:(DEFMACRO BAZ ...)。文件编译器不仅会编译宏并记录其存在,还会在编译时使宏可用。它被加载到编译器的环境中。

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

(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
EVAL-WHEN的示例:
 (eval-when (:compile-toplevel :execute :load-toplevel)
   (defun bar (form) ...)
 )

 (defmacro baz (form) (bar form))

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

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

如果函数在文件编译完成后不再需要,则只需使用:compile-toplevel即可。如果稍后需要使用它,则需要确保其已经被加载。

因此,EVAL-WHEN允许指定特定代码段应该在何时运行:

  • 文件编译期间
  • 文件加载期间
  • 执行期间

EVAL-WHEN在用户代码中并不经常使用。如果使用它,应该问自己是否真正需要它。


1
你好Rainer,但是为什么我们在使用eval-when时需要使用:execute和:load-top-level标志呢? - utxeee
1
Rainer 再次感谢您的回复并编辑之前的答案 :D 但我的疑问仍然存在,为什么我需要使用标志:load-top-level 和 execute。从您的文本中,我可以看出如果稍后使用该函数,则应使用标志:load-top-level。那么为什么我们不需要在所有其他稍后将使用的函数中使用 eval-when 和 load-top-level 标志呢? - utxeee
1
@utxeee:在编译时,如果没有 EVAL-WHEN,load-top-level 是默认设置。 - Rainer Joswig
Rainer,还有两个问题:(1)-如果我们使用您的示例从源代码加载文件,则不需要使用eval-when运算符,因为bar定义已知,对吗? (2)-为什么我们需要使用关键字:execute-据我所知,此关键字与运行时相关,但在运行时函数的定义也已知,对吗? - utxeee
2
请问您能否修复打字错误 load-top-level ==> :load-toplevel?我不得不深入源代码才找到它。 - ealfonso
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接