2010-01-20 51 views
4

我寫了這個快速排序功能:是否有可能在lisp中將遞歸函數重寫爲宏?

(defun quicksort (lst) 
    (if (null lst) 
     nil 
     (let ((div (car lst)) 
      (tail (cdr lst))) 
     (append (quicksort (remove-if-not (lambda (x) (< x div)) tail)) 
       (list div) 
       (quicksort (remove-if  (lambda (x) (< x div)) tail)))))) 

,但我不能把它改寫爲宏,它不工作,也沒有,例如,這個簡單的FOO(遞歸總和 - 我知道,有點傻,但正如爲例):

(defun Suma (lst) 
    (if (cdr lst) 
     (+ (Suma (cdr lst)) 
     (car lst)) 
     (car lst))) 

正常工作,但宏:

(defmacro SumaMacro (lst) 
    '(if (cdr lst) 
     '(+ (prog (SUMAMACRO (cdr lst))) 
      (prog (car lst))) 
     '(car lst))) 

似乎是錯誤的。有人有任何關於重寫遞歸函數的建議嗎?

回答

4

你在混合宏和運行時;換句話說,你在混合數值和語法。這裏是一個非常簡單的例子:

(defmacro while (condition &body body) 
    `(when ,condition ,@body (while ,condition ,@body))) 

這裏的壞事是宏不執行身體,它只是構造一個給定的身體一段代碼。所以,當函數中存在那種循環時,它會受到一些條件的保護,如if,這將防止無限循環。但是在這個宏代碼中沒有這樣的條件 - 你可以看到宏擴展到了原來的形式,這意味着它試圖擴展到一些無限的代碼。這就好像你已經寫了

(defun foo (blah) 
    (cons 1 (foo blah))) 

然後將該生成器函數掛鉤到編譯器中。所以要做這些類型的運行時循環,你必須使用一個真正的函數。 (當需要時,您可以使用labels創建一個本地函數來執行遞歸工作。)

4

將諸如SUM或QUICKSORT這樣的遞歸函數編寫爲宏是沒有意義的。另外,不,一般來說這是不可能的。宏擴展了源代碼。在編譯時,宏只能看到源代碼,但不能看到代碼被調用的真實參數。編譯之後,宏就消失了,並替換爲它生成的代碼。然後這個代碼隨後被參數調用。所以宏不能在編譯時根據僅在運行時已知的參數值進行計算。

例外情況是:當在編譯時/宏擴展時已知參數值時,宏可擴展爲對其自身的遞歸宏調用。但是,這是真正的高級宏使用,沒有什麼可以添加到其他程序員維護的代碼中。

經驗法則:如果你想做遞歸計算,然後使用函數。如果你想處理源代碼,那麼使用宏。

另外,嘗試使用類似Lisp的格式。編輯器計算括號,突出顯示和縮進。不要把括號放在他們自己的路線上,他們會感到孤獨。通常的Lisp風格更加緊湊,並且更多地使用水平空間。如果您使用列表,則使用FIRST和REST,而不是CAR和CDR。

你的 'SUMA' 功能應該是這樣的:

(defun suma (list) 
    (if (rest list) 
     (+ (suma (rest list)) 
     (first list)) 
    (first list))) 

忘掉宏。但是,如果您想了解更多關於宏的知識,那麼Paul Graham的書(On Lisp)(可下載)是一個很好的知識來源。

+0

對不起,但我冒昧地將提問者的代碼格式化爲標準的Lisp風格。 – Svante 2010-01-20 21:35:18

+0

我認爲最好讓它看起來有所不同。 ;-) – 2010-01-20 21:36:59

+0

有點挑剔:在第一段中,不應該是「宏觀擴展時間」而不是「編譯時間」嗎? – Svante 2010-01-21 19:25:37