2011-11-22 80 views
9

宏的這種遞歸定義做什麼它應該(和整數從1到n):如何遞歸宏定義得到評估

(defmacro sum-int-seq (n) 
    `(cond 
    ((equal 0 ,n) 0) 
    (t (+ ,n (sum-int-seq (- ,n 1)))))) 

例如(sum-int-seq 5)給15

但是爲什麼它工作?當宏得到擴展我得到這個:

(macroexpand '(sum-int-seq 5)) 
(IF (EQUAL 0 5) 0 (+ 5 (SUM-INT-SEQ (- 5 1)))) 

但是,因爲sum-int-seq是一個宏,宏評估應該成爲一個無限循環。編譯器是否會創建一個遞歸函數?如果這個定義創建了一個遞歸函數,還有什麼辦法可以遞歸地定義宏?

(這是爲簡明起見傻例如,函數當然會爲這個工作得更好)

回答

14

您的示例不起作用。

它可能在解釋器中工作。但是使用編譯器,編譯期間會看到無限循環。

CL-USER 23 > (defun test (foo) 
       (sum-int-seq 5)) 
TEST 

讓我們用LispWorks解釋:

CL-USER 24 > (test :foo) 
15 

讓我們嘗試編譯功能:

CL-USER 25 > (compile 'test) 

Stack overflow (stack size 15997). 
    1 (continue) Extend stack by 50%. 
    2 Extend stack by 300%. 
    3 (abort) Return to level 0. 
    4 Return to top loop level 0. 

Type :b for backtrace or :c <option number> to proceed. 
Type :bug-form "<subject>" for a bug report template or :? for other options. 

所以,現在下一個問題:爲什麼它在解釋工作,但編譯器無法編譯它?

好的,我會解釋它。

讓我們先看翻譯。

  • 它看到(sum-int-seq 5)
  • 它將其展開至(COND ((EQUAL 0 5) 0) (T (+ 5 (SUM-INT-SEQ (- 5 1)))))
  • 然後評估上面的表格。它確定它需要計算(+ 5 (SUM-INT-SEQ (- 5 1)))。爲此,它需要宏觀展開(SUM-INT-SEQ (- 5 1))
  • 最終它會擴展成類似(cond ((EQUAL 0 (- (- (- (- (- 5 1) 1) 1) 1) 1)) 0) ...的東西。然後將返回0,計算可以使用此結果並將其他條件添加到它。

解釋器獲取代碼,評估它可以和宏展開,如果有必要。生成的代碼然後被評估或宏展開。等等。

現在讓我們看看編譯器。

  • 它看到(sum-int-seq 5)並將其展開爲(COND ((EQUAL 0 5) 0) (T (+ 5 (SUM-INT-SEQ (- 5 1)))))
  • 現在宏展開將最終在子表單上完成。
  • 編譯器將宏展開(SUM-INT-SEQ (- 5 1))。請注意,代碼永遠不會被評估,只會展開。
  • 編譯器將宏展開和(SUM-INT-SEQ (- (- 5 1) 1))等等。最後你會看到堆棧溢出。

編譯器走(遞歸編譯/展開)代碼。它可能不會執行代碼(除非它優化或宏實際上明確地評估它)。

對於遞歸宏,您需要實際倒計時。如果你在宏裏面進行評估,那麼(sum-int-seq 5)就可以工作。但是對於(defun foo (n) (sum-int-seq n))這是沒有希望的,因爲編譯器不知道n的值是什麼。

+0

哇,謝謝你的詳細解答。 – snowape

2

擴展宏生成Lisp代碼然後被評估。調用一個函數將執行流轉移到之前運行的預先存在的lisp代碼的副本。除此之外,這兩者非常相似,並且遞歸以相同的方式工作。尤其是,宏擴展停止的原因與正確編寫的遞歸函數停止的原因相同:因爲存在終止條件,並且寫入了一個調用和下一個調用之間的轉換,以便實際達到該條件。如果沒有達到,宏擴展將進入一個循環,就像一個不正確編寫的遞歸函數。

2

對於Kilan的回答,我會補充一點,那就是macroexpand不必在表單中產生所有宏的完整擴展,直到沒有宏留下:)如果你看看Hyperspec,你會看到,它評估整個表格,直到它不是一個宏(在你的情況下它停在if)。在編譯期間,所有的宏都被擴展了,就好像macroexpand被應用到了源代碼樹的每個元素,而不僅僅是它的根。

3

另外還有一件事需要補充:在你的例子中,宏內部sum-int-seq的出現位於帶引號的表達式內,所以在宏被評估時它不會被擴展。這只是調用宏之前的數據。並且由於它嵌套在cond內部,所以在運行時,只有在條件爲真時纔會調用內部宏,與在常規函數中相同。

+0

謝謝,正是我一直在尋找的信息種類 – snowape

2

這裏是一個沒有工作的落實:

(defmacro sum-int-seq (n) 
    (cond 
    ((equal 0 n) `0) 
    (t `(+ ,n (sum-int-seq ,(- n 1)))))) 

它可以編寫一個遞歸宏,但(如被提及),擴展必須能夠在編譯時打基本情況。所以傳遞給宏的所有參數的值必須在編譯時已知。

(sum-int-seq 5) 

作品,但

(sum-int-seq n) 

沒有。