eval-when的一个必要用途是确保宏所依赖的函数在编译和使用宏时可用。但是,我想不出一个例子来演示不使用eval-when的后果。
(defpackage :eval-when
(:use :cl))
(in-package :eval-when)
(defun util-fun (x) (* x x))
(defmacro needs-help (x) `(let ((a (util-fun ,x))) a))
;; use it in the same file
(defun use-the-macro (x) (needs-help x))
(use-the-macro 5)如果我理解正确的话,(defun util-fun ...)应该用eval-when包装。
编辑:您将从答案中看到,这个示例有一个问题:它实际上在编译时不调用UTIL-。这解释了为什么没有错误,因为它不是一个错误。但是这个问题仍然有效,因为它突出了一个新用户的困惑。
但是,在编译、加载或使用期间,REPL没有发出错误或警告(SBCL 1.3.20):
; SLIME 2.19
CL-USER> (uiop:getcwd)
#P"/home/anticrisis/dev/common-lisp/eval-when/"
CL-USER> (compile-file "eval-when.lisp")
; compiling file "/home/anticrisis/dev/common-lisp/eval-when/eval-when.lisp" (written 14 AUG 2017 11:30:49 AM):
; compiling (DEFPACKAGE :EVAL-WHEN ...)
; compiling (IN-PACKAGE :EVAL-WHEN)
; compiling (DEFUN UTIL-FUN ...)
; compiling (DEFMACRO NEEDS-HELP ...)
; compiling (DEFUN USE-THE-MACRO ...)
; compiling (USE-THE-MACRO 5)
; /home/anticrisis/dev/common-lisp/eval-when/eval-when.fasl written
; compilation finished in 0:00:00.009
#P"/home/anticrisis/dev/common-lisp/eval-when/eval-when.fasl"
NIL
NIL
CL-USER> (in-package :eval-when)
#<PACKAGE "EVAL-WHEN">
EVAL-WHEN> (use-the-macro 3)
; Evaluation aborted on #<UNDEFINED-FUNCTION USE-THE-MACRO {10035E1103}>.
EVAL-WHEN> (needs-help 4)
; Evaluation aborted on #<UNDEFINED-FUNCTION UTIL-FUN {100387FE33}>.
EVAL-WHEN> (load "eval-when.lisp")
T
EVAL-WHEN> (use-the-macro 3)
9
EVAL-WHEN> (needs-help 4)
16
EVAL-WHEN> 注意,通常我使用occurs来评估并将一个文件加载到repl,但是在这里,我使用compile-file和load命令来演示没有发生错误。(在编译函数之后,在加载函数之前,尝试使用这些函数时,确实会收到错误,但是在任何卸载的代码中都会出现错误。)
以前有一些与此有关的问题和评论:
eval-when表单封装,或者加载到单独的文件中。既然如此,为什么我的例子不产生错误呢?在其他情况下,我的例子会失败吗?在没有正确使用eval-when时生成编译时、加载时或运行时错误的示例将有助于我的理解。
感谢您的耐心!
发布于 2017-08-14 22:07:37
记住
EVAL-WHEN是为了告诉文件编译器是否应该在编译时执行代码(例如,函数定义通常不执行),以及是否应该安排编译文件中的编译代码在加载时执行。这只适用于顶级表单。
通用Lisp在完整的Lisp环境中运行文件编译器(请记住,我们正在讨论编译文件,而不是在REPL中执行),并且可以在编译时运行任意代码(例如,作为开发环境工具的一部分,生成代码,优化代码等等)。如果文件编译器想要运行代码,那么文件编译器就需要知道定义。
还请记住,在宏展开期间,将执行宏的代码以生成展开的代码。宏本身为计算代码而调用的所有函数和宏都需要在编译时可用。在编译时不需要可用的是宏表单扩展到的代码。
这有时是一个混乱的根源,但它可以学习,然后使用它并不是太难。但令人困惑的是,文件编译器本身是可编程的,可以在编译时运行Lisp代码。因此,我们需要理解代码可能在不同情况下运行的概念:在REPL中,在加载时,在编译时,在宏展开期间,在运行时,等等。
还请记住,当您编译一个文件时,如果编译器需要稍后调用它的部分,那么您需要加载该文件。如果只是编译了一个函数,那么文件编译器将 not 存储在编译时环境中,也不会在完成文件编译之后存储代码。如果需要执行代码,则需要加载编译后的代码->或使用EVAL-WHEN ->,参见下面。
您的代码
您的代码在编译时不调用函数util-fun。因此,函数而不是需要在编译时环境中可用。
示例
另一个例子,函数实际上被调用,请参见下面。这是由compile-file编译的Lisp文件中的代码。
(defun run-at-compile-time ()
(print 'I-am-called-at-compile-time))
(defmacro foo ()
(run-at-compile-time) ; this function is called for its
; side-effect: it prints something
'(print 'I-am-called-at-runtime)) ; this code is returned
(foo) ; we use the macro in our code, the compiler needs to expand it.
; Thus during macro expansion the function
; RUN-AT-COMPILE-TIME will be called.因此,在宏展开期间,宏foo喜欢调用函数run-at-compile-time,该函数是在同一个文件中定义的。因为它在编译时环境中不可用,所以这是一个错误。文件编译器只生成要存储在磁盘上的函数的代码,这样当加载编译后的文件时,函数就会被定义。但是它没有定义在Lisp内部运行编译器->的函数,文件编译器不能调用它。
EVAL-WHEN 引入
为了让编译器也让编译时环境知道它,您需要将它包装在一个EVAL-WHEN中,并添加:compile-toplevel情况。然后,当文件编译器在toplevel上看到函数时,它运行定义的宏。
(eval-when
(:compile-toplevel ; this top-level form will be executed by the
; file compiler
:load-toplevel ; this top-level form will be executed at load-time
; of the compiled file
:execute) ; executed whenever else
(defun run-at-compile-time ()
(print 'I-am-called-at-compile-time))
)你也可以只提到一两种情况。例如,当文件编译器在顶层看到表单时,只有在那时才能执行该表单。它不会在加载时或其他情况下执行。
https://stackoverflow.com/questions/45665951
复制相似问题