Common lisp 在CommonLisp中,何时需要使用eval何时以及如何知道?

Common lisp 在CommonLisp中,何时需要使用eval何时以及如何知道?,common-lisp,compile-time,eval-when,Common Lisp,Compile Time,Eval When,当时,必须使用eval,以确保宏所依赖的函数在编译和使用宏时可用。但是,我想不出一个例子来说明在时不使用eval的后果 (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-th

当时,必须使用
eval,以确保宏所依赖的函数在编译和使用宏时可用。但是,我想不出一个例子来说明在
时不使用
eval的后果

(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包装

(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)
EDIT:从答案中可以看出,这个示例存在一个问题:它实际上在编译时不调用UTIL-FUN。这解释了为什么没有给出错误,因为它不是错误。但这个问题仍然有效,因为它突出了新用户的困惑

但是,从REPL来看,在编译、加载或使用过程中没有发出错误或警告(SBCL 1.3.20):

;黏液2.19
CL-USER>(uiop:getcwd)
#P“/home/anticrisis/dev/common lisp/eval when/”
CL-USER>(编译文件“eval when.lisp”)
; 编译文件“/home/anticrisis/dev/common-lisp/eval-when/eval-when.lisp”(写于2017年8月14日上午11:30:49):
; 编译(DEFPACKAGE:EVAL-WHEN…)
; 编译(包内:EVAL-WHEN)
; 编译(DEFUN UTIL-FUN…)
; 编译(DEFMACRO需要帮助…)
; 编译(定义使用宏…)
; 编译(使用宏5)
; /home/anticrisis/dev/common lisp/eval-when/eval-when.fasl已写入
; 编译在0:00:00.009完成
#P“/home/anticrisis/dev/common-lisp/eval-when/eval-when.fasl”
无
无
CL-USER>(包中:评估时间)
#
EVAL-WHEN>(使用宏3)
; 对#的评估已中止。
EVAL-WHEN>(需要帮助4)
; 对#的评估已中止。
EVAL-WHEN>(加载“EVAL-WHEN.lisp”)
T
EVAL-WHEN>(使用宏3)
9
EVAL-WHEN>(需要帮助4)
16
EVAL-WHEN>
请注意,通常我使用C-C-k来计算并将文件加载到repl,但在这里,我使用
编译文件
加载
命令来证明没有错误发生。(当我在编译函数之后但在加载函数之前尝试使用这些函数时,确实会收到一个错误,但任何卸载的代码都会出现这种情况。)

之前有与此相关的问题和意见:

  • 这似乎非常清楚地表明,宏使用的任何函数在
窗体时都必须包含在
eval中,或者加载到单独的文件中

  • coredump的评论也非常清楚:


    展开宏时,宏调用的任何函数都必须 定义如果您有一个定义宏的编译单元 调用函数,但实际上不在同一个函数中使用宏 编译单元,在编译时不需要eval。但是,如果您定义了 辅助的。函数,一个宏,并希望在完成后立即使用您的宏 定义它,那么实现可能会抱怨aux。 函数未知–coredump

  • 既然如此,为什么我的示例不生成错误?我的例子在其他情况下会失败吗?举例说明在
    时未能正确使用
    eval时生成的编译时、加载时或运行时错误有助于我的理解


    谢谢你的耐心

    记住

    EVAL-WHEN用于告诉文件编译器是否应该在编译时执行代码(函数定义通常不这样做),以及是否应该在加载时安排编译文件中的编译代码执行。这仅适用于顶级表单

    Common Lisp在完整的Lisp环境中运行文件编译器(请记住,我们谈论的是编译文件,而不是在REPL中执行),并且可以在编译时运行任意代码(例如,作为开发环境工具的一部分,生成代码、优化代码等)。如果文件编译器想要运行代码,那么文件编译器需要知道定义

    还要记住,在宏扩展期间,宏的代码会被执行以生成扩展的代码。宏本身为计算代码而调用的所有函数和宏都需要在编译时可用。编译时不需要的是宏表单扩展到的代码

    这有时会造成混乱,但可以学习,然后使用它并不难。但令人困惑的是,文件编译器本身是可编程的,可以在编译时运行Lisp代码。因此,我们需要理解代码可能在不同情况下运行的概念:在REPL中、在加载时、在编译时、在宏扩展期间、在运行时,等等

    还要记住,编译文件时,如果编译器以后需要调用部分文件,则需要加载该文件。如果函数刚刚编译,文件编译器将将代码存储在编译时环境中,也不会在完成文件编译后存储。如果需要执行代码,则需要加载编译后的代码->或使用EVAL-WHEN->见下文

    您的代码

    您的代码不会在编译时调用函数
    util fun
    。因此,函数不需要在编译时环境中可用

    示例

    另一个实际调用函数的示例,请参见下文。这是Lisp文件中的代码,由
    compilefile
    编译

    (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.
    
    因此,在宏扩展期间,宏
    foo
    喜欢调用函数
    run at compile time
    ,该函数在同一文件中定义。由于它在编译时环境中不可用,因此这是一个错误。文件编译器只为要存储在磁盘上的函数生成代码,这样在加载编译后的文件时,就可以定义函数。但是它没有在运行编译器的Lisp中定义函数->文件编译器无法调用它

    引入EVAL-WHEN(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)) )