我怎样才能避免这个编译错误?

How can I avoid this compilation error?

我有生成代码的代码(如 say-hello),我发现用生成的代码创建函数的最简单方法是将 defun 放在 eval 中,例如所以:

(defun say-hello ()
  `(princ "test"))

(eval
 `(defun test ()
    ,(say-hello)))

(test)

此文件在命令行上运行良好:

sbcl --script test.lisp

但是当我尝试在 SBCL 中编译它时抱怨:

* (compile-file "test.lisp")

; compiling file "test.lisp" (written 19 APR 2018 01:05:19 PM):
; compiling (DEFUN SAY-HELLO ...)
; compiling (EVAL (SB-INT:QUASIQUOTE #))
; compiling (TEST)
; file: test.lisp
; in: TEST
;     (TEST)
;
; caught STYLE-WARNING:
;   undefined function: TEST
;
; compilation unit finished
;   Undefined function:
;     TEST
;   caught 1 STYLE-WARNING condition

; test.fasl written
; compilation finished in 0:00:00.031
#P"test.fasl"
T
NIL
*

有什么方法可以让我编写代码来避免此编译错误?从生成代码的函数创建代码是否有更好的习惯用法?

函数 vs 宏 - 文件编译器

文件编译器对待函数和宏定义的方式略有不同:

  • 函数:文件编译器编译它们并且可以使用关于它们的信息。它将生成的代码转储到已编译的 fasl 文件中。但它不会在编译时环境.

  • 中创建可执行定义
  • 宏:文件编译器编译它们并使它们在编译时环境中可用。因此,编译器可以使用同一文件中较早定义的宏来扩展该宏在同一文件中的使用。

编译文件时表格的情况

  • :compile-toplevel -> file compile 在编译文件时评估它,并且形式在 toplevel

  • :load-toplevel -> 文件编译器生成代码以便在加载

  • 期间执行toplevel形式

您的代码

如果你想在定义它的同一个文件中使用一个函数,你需要告诉编译器在编译时实际执行定义:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun fn-say-hello ()
    `(princ "test")))

稍后在文件中宏可以使用该功能。编译器自动知道宏定义,但不知道函数。

(defmacro say-hello ()
  (fn-say-hello))  ; this is a function call, not returned code

以后就可以在文件中使用这个宏了。然后编译器将扩​​展该宏和 运行 函数 fn-say-hello - 然后就可以工作了,因为我们之前已经告诉编译器有关该函数的信息。

(defun test ()
  (say-hello))

(test)

玩过 EVAL-WHEN 之后,我也意识到我可以使用 #. reader 宏,而不是定义一个一次性的宏:

;; EVAL-WHEN makes say-hello available at compile time
(eval-when (:compile-toplevel :load-toplevel :execute) 
  ;; Define the function we want to use
  (defun say-hello ()
    `(princ "test")))

(defun test ()
  #.(say-hello)) ; Evals say-hello in-place at compile time

(test)