2016-09-16 50 views
4

我寫了一個宏來做多個嵌套循環。我知道還有其他設施可以做到這一點,但我正在努力學習如何編寫宏,這看起來是一個很好的用例。它的工作原理太(種):在Common Lisp中正確傳遞列表給一個宏

(defmacro dotimes-nested (nlist fn) 
    (let ((index-symbs nil) 
     (nlist (second nlist))) ;remove quote from the beginning of nlist 
    (labels 
     ((rec (nlist) 
      (cond ((null nlist) nil) 
        ((null (cdr nlist)) 
        (let ((g (gensym))) 
        (push g index-symbs) 
        `(dotimes (,g ,(car nlist) ,g) 
         (funcall ,fn ,@(reverse index-symbs))))) 
        (t (let ((h (gensym))) 
         (push h index-symbs) 
         `(dotimes (,h ,(car nlist) ,h) 
          ,(rec (cdr nlist)))))))) 
     (rec nlist)))) 

運行:

(macroexpand-1 '(dotimes-nested '(2 3 5) 
       #'(lambda (x y z) (format t "~A, ~A, ~A~%" x y z)))) 

輸出:

(DOTIMES (#:G731 2 #:G731) 
    (DOTIMES (#:G732 3 #:G732) 
    (DOTIMES (#:G733 5 #:G733) 
     (FUNCALL #'(LAMBDA (X Y Z) (FORMAT T "~A, ~A, ~A~%" X Y Z)) #:G731 #:G732 
       #:G733)))) 

,並呼籲像這樣的宏:

(dotimes-nested '(2 3 5) 
       #'(lambda (x y z) (format t "~A, ~A, ~A~%" x y z))) 

返回正確什麼我預計:

0, 0, 0 
0, 0, 1 
0, 0, 2 
0, 0, 3 
0, 0, 4 
0, 1, 0 
0, 1, 1 
0, 1, 2 
0, 1, 3 
0, 1, 4 
0, 2, 0 
0, 2, 1 
0, 2, 2 
0, 2, 3 
0, 2, 4 
1, 0, 0 
1, 0, 1 
1, 0, 2 
1, 0, 3 
1, 0, 4 
1, 1, 0 
1, 1, 1 
1, 1, 2 
1, 1, 3 
1, 1, 4 
1, 2, 0 
1, 2, 1 
1, 2, 2 
1, 2, 3 
1, 2, 4 
2 

但是,如果我這樣稱呼它:

(let ((dims '(3 4))) 
    (dotimes-nested dims 
       #'(lambda (x y) (format t "~A, ~A~%" x y)))) 

我得到一個錯誤:

; in: LET ((DIMS '(3 4))) 
;  (DOTIMES-NESTED DIMS #'(LAMBDA (X Y) (FORMAT T "~A, ~A~%" X Y))) 
; 
; caught ERROR: 
; during macroexpansion of (DOTIMES-NESTED DIMS #'(LAMBDA # #)). Use 
; *BREAK-ON-SIGNALS* to intercept. 
; 
; The value DIMS is not of type LIST. 

;  (LET ((DIMS '(3 4))) 
;  (DOTIMES-NESTED DIMS #'(LAMBDA (X Y) (FORMAT T "~A, ~A~%" X Y)))) 
; 
; caught STYLE-WARNING: 
; The variable DIMS is defined but never used. 
; 
; compilation unit finished 
; caught 1 ERROR condition 
; caught 1 STYLE-WARNING condition 

我如何通過在不被解釋爲一個符號的可變但是它的價值?我應該使用宏來評估它嗎?但我無法弄清楚如何。這兩種情況如何工作,a:用文字'(3 4)調用宏,b:傳遞一個綁定到列表的符號'(3 4)?我需要單獨的宏嗎?

最後,我有第二個問題。 當我傳入nlist的文字時,我必須使用(second nlist)才能獲取列表值。我知道這是因爲'(3 4)在被髮送到宏之前被擴展爲(quote (3 4))。這是一個正確的假設嗎?如果是這樣,這是一般的做法,即 - 使用傳遞給宏的文字列表的第二個值?

回答

5

宏在代碼編譯或運行之前展開。然而,只有在代碼運行時才存在變量DIMS。給宏的參數是文本數據,所以當你

(dotimes-nested dims ...) 

DIMS是不變量的引用(這還不存在),但只是個符號。

調用宏時也不必引用列表,因爲它是字面數據。所以你應該把它叫做(dotimes-nested (2 3 4) ...)(並且不需要刪除宏中的任何東西)。

如果您確實需要能夠使用維度的(運行時)變量,則應該使用常規函數而不是宏。喜歡的東西:

(defun dotimes-nested (nlist function) 
    (labels ((rec (nlist args) 
      (if (endp nlist) 
       (apply function (reverse args)) 
       (destructuring-bind (first . rest) nlist 
        (dotimes (i first) 
        (rec rest (cons i args))))))) 
    (rec nlist nil))) 

CL-USER> (let ((dims '(3 4))) 
      (dotimes-nested dims 
          #'(lambda (x y) (format t "~A, ~A~%" x y)))) 
0, 0 
0, 1 
0, 2 
0, 3 
1, 0 
1, 1 
1, 2 
1, 3 
2, 0 
2, 1 
2, 2 
2, 3 
NIL 
3

在你使用的宏我點,你有引號的文字列表,並在您的實現,你居然跟評論; remove quote from the beginning of nlist

宏適用second的拍攝代碼輸入和應用宏函數給那些未被評估的表達式,那純粹是數據只是指向表面語法,結果是新的代碼。這個代碼可以在每個使用它的地方使用宏代替。

擴展發生一次。通常函數存儲時函數中的所有宏都被擴展,並且在函數中沒有存在宏的痕跡。

當你有這樣的:

(dotimes-nested dims 
       #'(lambda (x y) (format t "~A, ~A~%" x y)))) 

宏獲取符號dims。因此,您的結果代碼應該有dims,以便在運行時可以評估爲列表,因爲您不知道在宏擴展時它可能是什麼。

我會做這樣的代替:

關於這樣做的好處是,你可以在這裏使用變量:

(defparameter *x* 10) 
(defparameter *y* 10) 
(dotimes* ((x *x*) (y *y*)) 
    (format t "(~a,~a)~%" x y)) 

如果你有一個變量的動態號碼,然後進行它的功能就像@ kiiski的答案一樣,會更好地匹配。