2013-10-15 64 views
2

這裏是我的宏定義:未定義的變量,而unquoting宏觀

*(defmacro run-test (test) 
    `(format t "Run test ~a ... ~a" ',test ,test)) 
*(run-test (= 1 1)) 
Run test (= 1 1) ... T 
NIL 

一切正常,現在,我已經定義了一個第二個宏(運行多個測試):

*(defmacro run-tests (&body body) 
    `(loop for tc in ',body 
     do (run-test tc))) 
* (run-tests (= 2 (1+ 1)) (= 1 1)) 
Run test TC ... (= 2 (1+ 1) Run test TC ... (= 1 1) 

這個結果並不是我想要的,我希望tc的每個值都被sexp替換,並且在運行測試中評估該值。我試着用

  do (run-test ,tc) 

更換線

  do (run-test tc) 

但這發出錯誤信號,

未定義的變量:TC

我怎樣才能改變這種獲得正確的結果?

回答

10

看看例如(run-tests (= 1 1))

(loop for tc in '((= 1 1)) do (run-test tc)) 

正如你看到的,代碼試圖調用(run-test tc)。但run-test在窗體上運行;當您通過一個包含表單的變量時它不起作用。

如果將代碼更改爲(run-test ,tc),則在宏展開時嘗試引用tc變量,但它僅在運行時綁定。

一個解決辦法是在宏擴展的時間做更多:

(defmacro run-tests (&body body) 
    `(progn ,@(loop for tc in body collect `(run-test ,tc)))) 
+0

我喜歡使用'loop'來構造宏展開的可能解決方案。一般來說,手工編寫至少一次代碼是宏應擴展到的_desired_代碼的有用方法。在這種情況下,它應該是'(progn(run-test form1)(run-test form2)...)',並且從這個角度看,'loop'顯然是獲得它的好方法。 –

+0

非常好的解決方案。我再次查看了LISP宏的細節。我錯了解宏觀擴展階段和運行階段。 –

0

只是作爲一個練習。您可以擺脫宏:

(defun run-test (test) 
    (format t "~%Run test ~a ... ~a" test (eval test))) 

(defun run-tests (tests) 
    (mapc 'run-test tests) 
    (values)) 

* (run-tests '((= 2 (1+ 1)) 
       (= 1 1))) 
+0

是的,實際上,作爲Python播放器,編寫函數對我來說很簡單。這僅僅是我學習LISP的宏。不管怎樣,謝謝。 –

+0

@WuLi不要使用宏,只要一個函數就足夠了。如果你絕對想要這樣做,你可能要考慮身體會返回什麼,它是一個函數(並且對於嵌套,這適用於想要擴展發生的站點的確切形式,而忽略發生哪些變量綁定發生爲了生效,畢竟宏是源代碼轉換)。 – Vatine

+0

@Vatine:是的,「從不使用函數就足夠的宏」是一個很好的經驗法則,但「eval是邪惡的」也是如此。我認爲一個宏是一個很好的方法來完成這個任務,如果做得對,並且具有不需要參數引用的優點。但是確實,'eval'方法更容易正確,而且由於測試函數旨在在開發過程中使用,而不是程序的實際運行時間,因此在這裏稍微可以接受。 –