2

Practical Common Lispの Ch 8 の最後で、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) の 2 番目のオカレンスがlimit自由変数として発生するため、潜在的にキャプチャされる、2) バインドされた変数 の最初のオカレンスがキャプチャされる可能性があるlimit、マクロ パラメーターに現れる他の変数と一緒に式で発生するため、 3) 順不同の評価。パラメーター リストの前に表示されますが、のdelta前に評価されます。4)とが複数回評価されるため、複数の変数評価。私が理解しているように、これらの問題を修正する必要があります。stopstopdeltastopstartonce-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、上で概説したすべての問題 (およびおそらく他の問題) を本当に解決しますか?

4

1 に答える 1

5

ONCE-ONLY マクロ

使用されていない警告を取り除くには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 を使用する

あなたの使用例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)))

なぜリストLIMITにあるのonce-onlyですか?limitそこでは未定義です。フォーム内ではシンボルとしてLIMIT使用されますが、外側ではバインディングはありません。ONCE-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-onlyfor を含む名前のバインディングを提供することを考えると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)))
于 2017-04-04T07:49:13.987 に答える