在第8章末尾Practical Common Lisp中,Peter Seibel提供了once-only
宏。其目的是通過用戶定義的宏來緩解一些微妙的問題,並對變量進行評估。注意我並不想在這一點上這個宏的作品,如在一些其他職位,但只是如何使用它正確地如何理解:使用`一次只'宏
(defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for n in names collect (gensym))))
`(let (,@(loop for g in gensyms collect `(,g (gensym))))
`(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
,@body)))))
以下是嘗試將樣品(不正確)做作宏展現出幾個可變的評估問題它宣稱一些增量迭代一個整數範圍,返回範圍:
(defmacro do-range ((var start stop delta) &body body)
"Sample macro with faulty variable evaluations."
`(do ((,var ,start (+ ,var ,delta))
(limit ,stop))
((> ,var limit) (- ,stop ,start))
,@body))
例如,(do-range (i 1 15 3) (format t "~A " i))
應打印1 4 7 10 13
,然後返回14
。
問題包括:1)潛在捕獲第二次出現limit
,因爲它作爲自由變量出現,2)潛在捕獲綁定變量的初始出現limit
,因爲它出現在表達式中以及其他變量出現在宏參數中,3)無序評估,因爲delta
將在stop
之前評估,即使stop
出現在參數列表中的delta
之前,並且4)多個變量評估,因爲stop
和start
被多次評估。據我瞭解,once-only
應該解決這些問題:
(defmacro do-range ((var start stop delta) &body body)
(once-only (start stop delta limit)
`(do ((,var ,start (+ ,var ,delta))
(limit ,stop))
((> ,var limit) (- ,stop ,start))
,@body)))
然而,(macroexpand '(do-range (i 1 15 3) (format t "~A " i)))
抱怨limit
是未綁定變量。如果我切換到with-gensyms
,這應該只處理上面的問題1 & 2,但擴展仍然沒有發生。
這是宏問題once-only
的問題嗎? once-only
是否真的解決了上面列出的所有問題(也許還有其他問題)?
爲什麼你'limit'可言?難道你不能用「一次性」版本中的',stop'替換所有用途嗎? – melpomene
@melpomene是的,你是對的。但我想不出一種更好的簡單方法來將兩種基本類型的變量捕捉問題引入到有缺陷的宏中(列爲上面的問題1和2)。任何其他想法? – davypough