マクロ展開に対する先入観

  • プログラマがコード中に書いたマクロ
  • マクロ展開によって出現するマクロ

すべてが展開された後に評価されるもんだという先入観があった。
だけど、id:yagiey:20100223:1266942998あたりからその先入観が疑わしく感じ始めた。


例えば次のような場合

gosh> (define-macro (_if test e1 . e2)
        `(if ,test ,e1 . ,e2))
#<undef>
gosh> (define-macro (_or . args)
        (cond
         ((null? args) #f)
         ((null? (cdr args)) (car args))
         (else
          (let ((tmp (gensym)))
            `(let ((,tmp ,(car args)))
               (_if ,tmp ,tmp (_or . ,(cdr args))))))))
#<undef>
gosh> (macroexpand '(_or #f #f 42))
(let ((#0=#:G2 #f)) (_if #0# #0# (_or #f 42)))

一回マクロ展開しただけだと、深いところにある_ifや_orは展開されない。
つまり、一回目のマクロ展開に後続する何らかの計算で、さらにマクロ展開が行われるということだな。つまり、僕の先入観が間違っていたわけだな。
それともmacroexpandは通常の評価の様子を反映しなかったりするのかな?


また一方で

gosh> (define-macro (hoge) (+ 1 2))
#<undef>
gosh> (macroexpand '(hoge))
3

のように、マクロ展開時にも普通に手続きの評価が行われる。


LISPのマクロ展開を利用したプログラミングをメタプログラミングととらえると、

  • マクロ展開
  • マクロ展開以後の計算

という2つの計算シーンがあるというイメージがあって、それがC++のテンプレートメタプログラミングとダブるもんだから、マクロ展開はそれ以降のすべての計算に先立つという先入観があった。


どうやら、LISPのマクロ展開は、自分が思っているほど特別な存在ではないようだ。マクロ展開とそれ以外の計算は互いに協調して動いているようだ。


じゃ、マクロ展開は、具体的にはいつ行われるんだろう。
このエントリのSchemeのコードはGaucheでしか確かめてないけど、もしかしたらすべて処理系依存な話しだったりもするのかな。

不毛な考察だった!!!

コメントの通り、

そもそも伝統的マクロは規格にありません。未定義です。

ということらしい。


そりゃないよぉぉぉぉぉぉぉぉぉぉぉーーーーー
ここまで好きにさせといて勝手に好きになったはないじゃない

コメント欄をまとめてみた(2010/03/15 00:09)

コメント欄すごいよ...。
SaitoAtsushiさん、shiroさん、ありがとうございました。


まず、自分がウダウダ考えてたことへの答え

  • macroexpandは一番外側のマクロしか展開しない
  • マクロ展開はすべての計算に先立って行われる

だそうだ。
後者は最新規格(r6rsかな?)ではっきり決められたそうだ。


あといくつも重要な情報を頂いた。

  • r5rsでは伝統的マクロは規格外
  • r6rsのマクロに関するドキュメント
  • マクロ展開のタイミングは、伝統的マクロか否かには関係ない
  • syntax-caseというマクロを使えば伝統的マクロを実装できる(gaucheはr6rsじゃないけどsyntax-caseが使える)
    • プログラミングGaucheの272ページ脚注「Gaucheは近い将来、コンパイラに統合されたsyntax-caseをサポートする予定ですが、(以下略)」
  • SCMという処理系はマクロ展開の仕組みがgaucheとは異なっていた(現在はどうか未確認)
    • 「そのマクロフォームが初めて実行される時点で展開され、展開結果がメモ化される」という実装


うれしすぎ。ブログやっててよかった!
僕もいつか処理系を実装してみたいなぁ。