Island Life

< 簡単で直交性の高い道具を組み合わせる | 生物の分類 >

2015/01/23

入れ子のバッククオート

自分も良く迷うんだけど、一応簡単な指針がある。

まず、コンマのネストレベルとバッククオートのネストレベルを常に合わせるようにする。 次は良くない例:

    ``(list ,(+ 1 2))

これは(list ...)が2重のバッククオートの中にあるのに中でコンマを1重にしか使ってない。 規約上はバッククオートよりコンマのネストが少なければ動作はするけど、わかりづらくなるので避ける。

次に、「どのレベルのバッククオートで式を展開したいか」を考えて、 機械的に次の指針をあてはめる:

  • 最初のレベル(外側)のバッククオートで式を展開したい場合はこうする:
    ,',式
  • 最初のレベルでは展開せず、二番目(内側)のバッククオートで展開したい場合はこうする:
    ,,'式

ネストしたバッククオートが出てくる典型例はマクロ定義を生成するマクロだけれど、 その場合、最初のマクロ展開で埋め込みたい式は前者、生成されたマクロ定義の展開時に コンマとして機能させたい場合は後者を使う。

ちょっと手元に例として引けるコンパクトなコードがないので、わざとらしい例だけど、 例えば

(defmacro foo (x) `'(foo ,x))

みたいなマクロを名前を指定して生成するマクロ

(defmacro gen-defmacro (name) ...)

を考えよう。(gen-defmacro foo) とすると上の(defmacro foo ...)が生成される。(gen-defmacro bar)とするとこれが生成される:

(defmacro bar (x) `'(bar ,x))

まず、ここまではすぐ書けるだろう:

(defmacro gen-defmacro (name)
  `(defmacro ,name (x) `'(...)))

ここのnameはまだバッククオートが1重なので迷わない。問題はバッククオートがネストする...の部分だ。

  • nameはgen-defmacroの展開時に埋め込みたい。つまり外側のバッククオートで展開
  • xは生成されたdefmacroの展開時に埋め込む、つまり内側のバッククオートで展開

上の指針をあてはめればこの通り。

(defmacro gen-defmacro (name)
  `(defmacro ,name (x) `'(,',name ,,'x)))

実行例

[1]> (defmacro gen-defmacro (name)
      `(defmacro ,name (x) `'(,',name ,,'x)))
GEN-DEFMACRO
[2]> (gen-defmacro foo)
FOO
[3]> (gen-defmacro bar)
BAR
[4]> (foo (a b c))
(FOO (A B C))
[5]> (bar (d e f)
(BAR (D E F))

3重以上のネストも基本的に同じ。ポイントは comma-quoteが互いにキャンセルするということ。後は右側から展開されてく。

    ,',',式   ; 一番外側のバッククオートで展開
    ,',,'式   ; 二番目のバッククオートで展開
    ,,','式   ; 三番目(最も内側)のバッククオートで展開

なお、バッククオートのネストレベルはコンマがあると下がるので、次のようなコードは ネストとは考えない:

    `(list ,(list `(list ,x)))

外側のバッククオートはすぐ次のコンマと対応するんで、2番めのlistの式はネストレベルが0になる。

Tags: Programming, CommonLisp, Macro

Post a comment

Name: