ant*_*sis 4 common-lisp compile-time eval-when
必要的用途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)
Run Code Online (Sandbox Code Playgroud)
如果我理解正确,(defun util-fun ...)
应该用eval-when
.
编辑: 正如你在答案中看到的那样,这个例子有一个问题:它在编译时实际上并没有调用UTIL-FUN.这解释了为什么没有给出错误,因为它不是错误.但问题仍然有效,因为它突出了新用户的困惑.
但是,从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>
Run Code Online (Sandbox Code Playgroud)
请注意,通常我使用Cc Ck来评估和加载文件到repl,但在这里,我使用compile-file
和load
命令来证明没有错误发生.(当我尝试在编译后但在加载之前使用这些函数时,我确实收到错误,但任何卸载的代码都会出现这种情况.)
先前有与此相关的问题和评论:
这以前StackOverflow的答案似乎很清楚地说,这是使用宏任何功能必须由封闭eval-when
的形式,或在一个单独的文件中加载.
来自coredump的评论也很清楚:
扩展宏时,必须定义宏调用的任何函数.如果你有一个编译单元来定义一个调用函数的宏,但你实际上并没有在同一个编译单元中使用宏,那么你就不需要eval-when.但是,如果您定义了辅助.函数,一个宏,并想在你定义它后立即使用你的宏,然后执行可能会抱怨aux.功能未知 - coredump
鉴于此,为什么我的示例不会产生错误?我的例子会在其他情况下失败吗?未能正确使用时生成的编译时,加载时或运行时错误的示例eval-when
将有助于我的理解.
感谢您的耐心等待!
记得
EVAL-WHEN是告诉文件编译器它是否应该在编译时执行代码(它通常不用于函数定义)以及它是否应该在编译文件中安排编译代码以在加载时执行.这仅适用于顶级表单.
Common Lisp在完整的Lisp环境中运行文件编译器(记住我们正在讨论编译文件,而不是在REPL中执行),并且可以在编译时运行任意代码(例如,作为开发环境工具的一部分,生成代码,到优化代码等).如果文件编译器想要运行代码,那么文件编译器需要知道定义.
还要记住,在宏扩展期间,宏的代码被执行以生成扩展代码.宏本身调用以计算代码的所有函数和宏都需要在编译时可用.编译时不需要的是宏表格扩展的代码.
这有时是混乱的根源,但它可以学习,然后使用它并不太难.但令人困惑的部分是文件编译器本身是可编程的,可以在编译时运行Lisp代码.因此,我们需要理解代码可能在不同情况下运行的概念:在REPL中,在加载时,在编译时,在宏扩展期间,在运行时等.
还要记住,当您编译文件时,如果编译器需要稍后调用其中的一部分,则需要加载该文件.如果刚刚编译了一个函数,文件编译器将不会在编译时环境中存储代码,也不会在完成文件编译后存储.如果您需要执行代码,那么您需要加载已编译的代码 - >或使用EVAL-WHEN - >参见下文.
你的代码
您的代码在编译时不会调用该函数.所以功能并没有需要在编译时环境可用.util-fun
一个例子
另一个实际调用函数的例子见下文.这是Lisp文件中的代码,由编译compile-file
.
(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.
Run Code Online (Sandbox Code Playgroud)
因此在宏扩展期间,宏foo
喜欢调用run-at-compile-time
在同一文件中定义的函数.由于它在编译时环境中不可用,因此这是一个错误.文件编译器仅生成要存储在磁盘上的函数的代码,这样当加载编译的文件时,函数就会被定义.但它没有定义运行编译器的Lisp内部的函数 - >文件编译器无法调用它.
介绍EVAL-WHEN
要告诉编译器也让编译时环境知道它,你需要将它包装在EVAL-WHEN中并添加:compile-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))
)
Run Code Online (Sandbox Code Playgroud)
您还可以提及一两种情况.例如,当文件编译器在顶层看到它时,可以执行该表单.它不会在加载时或其他情况下执行.