あなたはこれを見ていますか:
(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)))))
それほど複雑ではありませんが、ネストされた逆引用符と、互いに類似した複数のレベルがあり、経験豊富な Lisp コーダーでさえ混乱を招きやすいものです。
これは、展開を記述するためにマクロによって使用されるマクロです: マクロの本体の一部を書き込むマクロです。
マクロ自体の本体にはプレーンがありlet
、その後、一度バッククォートされて生成さlet
れ、 を使用するマクロの本体内に存在しますonce-only
。最後に、マクロがユーザーによって使用されるコード サイトで、そのlet
マクロのマクロ展開に表示される二重バッククォートがあります。
gensyms を生成する 2 回のラウンドが必要なのonce-only
は、それ自体がマクロであり、それ自体のために衛生的でなければならないからです。そのため、最も外側に自分自身のgensymsの束を生成しlet
ます。また、 の目的はonce-only
、別の衛生的なマクロの記述を簡素化することです。そのため、そのマクロの gensyms も生成されます。
一言で言えば、once-only
値が gensyms であるいくつかのローカル変数を必要とするマクロ展開を作成する必要があります。これらのローカル変数は、gensym を別のマクロ展開に挿入して衛生的にするために使用されます。そして、これらのローカル変数は、マクロ展開であるため、それ自体が衛生的でなければならないため、gensyms でもあります。
単純なマクロを書いている場合、gensyms を保持するローカル変数があります。
;; silly example
(defmacro repeat-times (count-form &body forms)
(let ((counter-sym (gensym)))
`(loop for ,counter-sym below ,count-form do ,@forms)))
マクロを作成する過程で、シンボルcounter-sym
. この変数はプレーン ビューで定義されます。人間であるあなたは、語彙の範囲内で何かと衝突しないようにそれを選択しました。問題のレキシカル スコープは、マクロのスコープです。counter-sym
内部の参照を誤ってキャプチャすることを心配する必要はありません。count-form
または、一部のリモート レキシカル スコープ (マクロが使用されるサイト) に挿入されるコードの一部に入る単なるデータであるforms
ためです。マクロ内の別の変数とforms
混同しないように注意する必要があります。counter-sym
たとえば、ローカル変数に名前を付けることはできませんcount-form
。なんで?その名前は関数の引数の 1 つだからです。それを隠して、プログラミング エラーを作成します。
マクロを使ってそのマクロを作成したい場合は、マシンがユーザーと同じ仕事をしなければなりません。コードを書いているときは、変数名を考案する必要があり、考案した名前に注意する必要があります。
ただし、コード作成マシンは、あなたとは異なり、周囲のスコープを認識しません。そこにある変数を単純に見て、衝突しない変数を選択することはできません。マシンは、いくつかの引数 (評価されていないコードの断片) を取り、そのマシンがその仕事を終えた後に盲目的にスコープに代入されるコードの断片を生成する単なる関数です。
したがって、マシンはより賢明に名前を選択する必要があります。実際、完全に防弾であるためには、偏執的であり、完全にユニークなシンボルであるgensymsを使用する必要があります.
例を続けて、このマクロ本体を作成するロボットがあるとします。そのロボットはマクロにすることができますrepeat-times-writing-robot
:
(defmacro repeat-times (count-form &body forms)
(repeat-times-writing-robot count-form forms)) ;; macro call
ロボット マクロはどのように見えるでしょうか?
(defmacro repeat-times-writing-robot (count-form forms)
(let ((counter-sym-sym (gensym))) ;; robot's gensym
`(let ((,counter-sym-sym (gensym))) ;; the ultimate gensym for the loop
`(loop for ,,counter-sym-sym below ,,count-form do ,@,forms))))
これが の特徴のいくつかをどのように持っているかを見ることができますonce-only
: 二重の入れ子と の 2 つのレベル(gensym)
。これが理解できれば、飛躍once-only
は小さいです。
もちろん、ロボットに繰り返し回数を書かせたいだけなら、それを関数にすれば、その関数は変数の発明について心配する必要がなくなります: それはマクロではないので、衛生は必要ありません:
;; i.e. regular code refactoring: a piece of code is moved into a helper function
(defun repeat-times-writing-robot (count-form forms)
(let ((counter-sym (gensym)))
`(loop for ,counter-sym below ,count-form do ,@forms)))
;; ... and then called:
(defmacro repeat-times (count-form &body forms)
(repeat-times-writing-robot count-form forms)) ;; just a function now
しかし、その仕事はボス、それを使用するマクロに代わって変数を発明することであり、関数は呼び出し元に変数を導入できないため、関数にすることはできませんonce-only
。