[英]Passing a list to a macro correctly in 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))
在被發送到宏之前被擴展為(quote (3 4))
。 這是正確的假設嗎? 如果是這樣,這是一般的做法,即 - 使用傳遞給宏的文字列表的第二個值?
在編譯或運行代碼之前擴展宏。 但是,變量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
在您對宏的使用中,我發現您引用了文字列表,並且在您的實現中,您實際上在注釋中應用了second
; remove quote from the beginning of nlist
; remove quote from the beginning of nlist
宏接受代碼輸入並將宏函數應用於那些未評估的表達式,這純粹是僅僅引用表面語法的數據,結果是新代碼。 然后可以在使用它的每個地方使用宏來替換此代碼。
擴張只發生一次。 通常,當存儲函數時,函數中的所有宏都會被擴展,並且當使用該函數時,不存在宏的痕跡。
當你有類似的東西:
(dotimes-nested dims
#'(lambda (x y) (format t "~A, ~A~%" x y))))
宏獲得符號dims
。 然后,您生成的代碼應該具有dims
以便在運行時通過實際計算到列表,因為您不知道在宏擴展時它可能是什么。
我會這樣做而不是:
(dotimes* ((a 3) (b 2) (c 10))
(format t "~A, ~A, ~A~%" a b c))
(defmacro dotimes* ((&rest binding-initials) &body body)
(loop :for (binding initial) :in (reverse binding-initials)
:for result := `(dotimes (,binding ,initial nil) ,@body)
:then `(dotimes (,binding ,initial nil) ,result)
:finally (return result)))
關於這一點的好處是你可以在這里使用變量:
(defparameter *x* 10)
(defparameter *y* 10)
(dotimes* ((x *x*) (y *y*))
(format t "(~a,~a)~%" x y))
如果你有一個動態數量的變量然后使它成為一個函數,比如@ kiiski的答案,那將是一個更好的匹配。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.