Gauche:applicable objectとコンパイラマクロ
Shiro(2021/04/25 01:41:05 UTC): Gaucheではコンパイラマクロを特殊なインライン可能手続きとして実現している。
コンパイラは、(f a b c ...)
というフォームを見て、
f
が構文キーワードでもマクロでもなく、- 定数束縛で、その値が手続きであることがわかっており、
- その手続きがインライン展開ハンドラを持っている
というケースで、インライン展開ハンドラを呼び出す。
このハンドラは以前はいくつかの組み込み関数のインライン展開用のカスタムハンドラか、
define-inline
で定義された場合の手続きの内容をその場に展開するハンドラだけだったのだが、
ここに「コンパイラマクロハンドラを呼び出す」というフックをかませることで、
コンパイラマクロがインライン展開フェーズで走るようになったのだ。
ところが、手続き以外のものにコンパイラマクロをつけたいケースが出てきた。
x
が手続きやジェネリックファンクションでなくても、
(x a b c ...)
と呼び出せるケースがある。
x
にobject-apply
メソッドが定義されている場合である。
で、その計算が純粋で、かつ引数が全部コンパイル時に値が決まっているなら、この呼び出しも コンパイル時に計算してしまいたい。
具体的に出てきたのは、抽象型の計算である。まだundocumentedだけど、
例えば (</> <integer> <string>)
で<integer>
または<string>
という
型が表現できて、(assume-type x (</> <integer> <string>))
といった形で
実行時型検査ができるようになってる。
んで、</>
の値自体はメタクラスに束縛されてるんだけど、そいつがobject-apply
メソッドを持ってるので、 (</> <integer> <string>)
と呼び出すと
「<integer>
または<string>
」という抽象型を表すクラスが作られる。
ここまではいいんだけど、(assume-type x (</> <integer> <string>))
を手続きの
引数の検査に使った場合、このままでは手続きが呼ばれる度に
(</> <integer> <string>)
の計算が走ってしまう。
こいつをコンパイル時に済ませておきたい。
しかし、</>
が手続きだったらコンパイラマクロがそのまま使えたんだけど、object-apply
経由だからそれができないなあ、となった。
インライン展開という意味にこだわるなら、object-apply
の方に何らかの機能を
持たせて展開をハンドルさせるという考え方もありだが、(f a b c ...)
フォームの
処理で f
の種別を判断するところは性能上のクリティカルパスにあって、
あんまりそこの判断にサイクルを割きたくない。
また、object-apply
は普通のジェネリックファンクションであるのが特徴だから
これだけに特殊な機能を持たせるのもいまいち。
とすると、(class-of f)
の側に何か持たせるべきかなあ。
クラスにexpanderを持たせることで、手続き以外のコンパイラマクロ化はできるんだけれど、 抽象型のコンパイル時計算をマクロ展開でやるには、 引数に渡されるクラスの実体をコンパイル時に取ってこないとならないのか。 現在のところ、マクロ展開器は識別子のレイヤは扱えるけどその中で実行時の値は取れない。
マクロ展開器内で実行時の値の取得を許すのはフェーズを逆方向に越えることになるので 意味的に微妙。また、constant foldingはマクロ展開の後に走るので、 マクロではまだ見えない定数値がある。
ということを考えると抽象型コンストラクタはコンパイラ内で認識して別扱いすべきかもなあ。
型情報をコンパイラ内で扱っておもしろいことをやるには、結局コンパイル時に型オブジェクトを 掴んでないとならないので、特別扱いは必要。なのでそっちの線で考えてみる。