2017-08-14 75 views
3

eval-when的必需用途是爲了確保在編譯和使用宏時,宏所依賴的函數是可用的。但是,我想不出一個能證明不使用eval-when的後果的例子。在Common Lisp中,您何時需要使用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樂趣。這解釋了爲什麼不給出錯誤,因爲它不是錯誤。但這個問題仍然有效,因爲它凸顯了新用戶的困惑。通常我使用抄送CK來評估和加載文件

; 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> 

注:

然而,從REPL,沒有錯誤或警告在編譯過程中,負載或使用(SBCL 1.3.20)發佈到repl,但在這裏,我使用compile-fileload命令來證明沒有錯誤發生。 (當我在編譯之後但在加載之前嘗試使用這些函數時,我確實收到錯誤,但是這會在任何卸載代碼時發生。)

有以下問題和意見涉及到:

  • previous StackOverflow answer似乎很清楚地說,這是用來由宏任何功能必須由eval-when形式被封閉,或者在一個單獨的文件中加載。

  • 從信息轉儲這一評論也很明確:

    當宏展開時,該宏調用必須 定義的任何功能。如果您有一個編譯單元定義了一個宏,其中的 稱爲函數,但實際上並沒有在同一個編譯單元中使用該宏,則不需要eval-when。但是,如果您定義了一個 aux。函數,一個宏,並且想在你定義它之後馬上使用你的宏,那麼實現可能會抱怨aux。 功能未知 - coredump

鑑於此,爲什麼我的示例不會生成錯誤?我的例子會在其他情況下失敗嗎?如果沒有正確使用eval-when會產生編譯時,加載時或運行時錯誤,這將有助於我的理解。

謝謝你的耐心等待!

+1

你可能想看看這個問題https://stackoverflow.com/questions/10674650/eval-when-uses。您也可以閱讀PCL的eval-when部分http://www.gigamonkeys.com/book/the-special-operators.html –

+0

@DavidHodge感謝您的推薦;我已經將我的問題擴大到更具體。 – anticrisis

+0

你提到的另一個答案是關於編譯文件的討論。你提到一個REPL。這是兩回事。還請發佈具體問題,最好用代碼。 –

回答

4

記住

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. 

所以宏膨脹期間宏foo喜歡調用函數run-at-compile-time,其在相同的文件中定義。由於它在編譯時環境中不可用,所以這是一個錯誤。文件編譯器只生成要存儲在磁盤上的函數的代碼,以便在編譯的文件加載時定義該函數。但是它並沒有在運行編譯器的Lisp中定義函數 - >文件編譯器不能調用它。

介紹EVAL-WHEN

要告訴編譯器也讓編譯時環境的瞭解它,你需要用它在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)) 

) 

您還可以提到一種或兩種情況。例如,表單可以在文件編譯器在頂層查看時執行,並且僅在此時執行。它不會在加載時或其他情況下執行。

+0

非常棒的解釋,謝謝,但還有一點我不知道你是否可以澄清:事實上,如果你的宏定義是在文件與實際使用它們的文件是分開的,那麼這個問題是永遠不可見的,並且eval-when是不必要的。我相信這導致了我自己的困惑 – anticrisis

+1

@anticrisis:那麼你必須確保文件,代碼(源代碼或編譯)依賴於o n在編譯依賴文件之前已加載(!)到編譯Lisp中。只編譯它們是不夠的。 –

相關問題