2017-04-03 29 views
2

在第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)多個變量評估,因爲stopstart被多次評估。據我瞭解,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是否真的解決了上面列出的所有問題(也許還有其他問題)?

+1

爲什麼你'limit'可言?難道你不能用「一次性」版本中的',stop'替換所有用途嗎? – melpomene

+0

@melpomene是的,你是對的。但我想不出一種更好的簡單方法來將兩種基本類型的變量捕捉問題引入到有缺陷的宏中(列爲上面的問題1和2)。任何其他想法? – davypough

回答

5

的僅一次宏觀

擺脫警告N是閒置的,我會改變宏:

(defmacro once-only ((&rest names) &body body) 
    (let ((gensyms (loop for nil in names collect (gensym)))) 
          ; changed N to NIL, NIL is ignored 
    `(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))))) 

這個宏的目的是爲了確保表達式僅以一個確定的順序進行一次評估。爲此,它將引入新的未中斷變量,並將評估結果綁定到這些變量。在他的宏裏面,新的變量是可用的。提供宏本身使編寫宏更容易。

在DO-RANGE使用一次性的

你的榜樣使用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))) 

爲什麼會出現LIMITonce-only列表? limit在那裏沒有定義。 LIMITONCE-ONLY表單中用作符號,但在外面沒有綁定。

ONCE-ONLY預計名稱列表是一個符號列表,並且這些名稱被綁定到表單。在你的情況limit是一個符號,但它是未定義的。

我們需要從名稱的列表中刪除limit

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta) 
    `(do ((,var ,start (+ ,var ,delta)) 
      (limit ,stop)) 
     ((> ,var limit) (- ,stop ,start)) 
     ,@body))) 

現在,該怎麼辦LIMIT?鑑於once-only提供綁定的名稱,包括STOP,我們可以消除符號LIMIT,stop替換它的使用方法:

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta) 
    `(do ((,var ,start (+ ,var ,delta))) 
     ((> ,var ,stop) (- ,stop ,start)) 
     ,@body))) 

例子:

CL-USER 137 > (pprint 
       (macroexpand 
       '(do-range (i 4 10 2) 
        (print i)))) 

(LET ((#1=#:G2170 4) 
     (#3=#:G2171 10) 
     (#2=#:G2172 2)) 
    (DO ((I #1# (+ I #2#))) 
     ((> I #3#) (- #3# #1#)) 
    (PRINT I))) 
+0

完美,完美!但僅僅爲了澄清我的理解,並不需要從'do'中消除'limit'。只要將它從'一次只能的'變量中刪除,原始錯誤的宏就可以運行。 – davypough

+0

@davypough:但是LIMIT在身體中可見,也可能隱藏另一個LIMIT。這是我們通常想避免的。 –