Gauche:FeatureIdentifier
Shiro: 2007/03/29 02:19:39 PDT
システムによって有ったり無かったりする機能を、ユーザプログラムにどうやってハンドルさせるか、という話。要はCで書くときの
#if defined(HAVE_FOO) ... fooを使ったコード ... #else ... 代替コード #endif
みたいなことをSchemeで出来るようにしたい。0.8.10に入れたいところだが、間に合わなければ もう少し後になるかも。
基本はcond-expand
基本方針として、こういう切り替えは全てcond-expandでやりたい。(GaucheRefj:cond-expand)
(if (global-variable-bound? ...) ...) 等の方法は、
- テストは実行時に行われるため、コンパイル時に解釈されるフォーム (use等) を切り替えられない
- 本来、ifやcondの中にトップレベル定義は書けない (今のGaucheは書けてしまうがそれはバグ)
という欠点があるため。
cond-expandを使うとなると、特定の機能があることを示すfeature-identifierを どう管理するか、ということを考えなければならない。特に、ユーザ提供の拡張モジュールに ついて、柔軟かつロバストにfeature-identifierをシステムに追加してゆく方法が 必要である。
要求仕様。
- feature-identifierは、対象となるコードがシステムにロードされる前に 見えてなければならない。例えば特定のfeatureがあるならそれを提供するモジュールを useしたい、という場合。あるいは拡張モジュール自身をコンパイルしている最中に cond-expandで定義されるものを切り替えたい場合。 拡張モジュールのロード時にfeature-identifier を追加する方法ではうまくいかない。
- Gauche本体のコンパイル限定の問題だが、コンパイルに使われるgosh内で定義されている featureと、コンパイルの対象となるgoshが持つであろうfeatureが異なる場合がある。 (例えば、pthread無しでコンパイル・インストールしたgoshを使ってpthread有りの goshをコンパイルする場合。) このことから、feature setをgaucheバイナリの 外部から指定することが可能でなければならない。
- central repositoryは避けたい。例えばSLIBはslibcatという単一ファイルに (requireで使う)feature symbolとパスのalistを保存しているが、 ユーザに自由に拡張パッケージを追加/削除させようとすると、 ひとつのファイルをインストール・アンインストールの度に触らなければならない。 特にアンインストールでミスって一貫性が失われるのが嫌だ。それに、 システム標準ではないディレクトリにユーザ権限でインストールされた拡張モジュールを どうするかって問題もある。
- 毎回ライブラリディレクトリをスキャンしたくない。central repositoryが無いとすれば 拡張パッケージ毎にfeature setを記述したファイルをインストールしてゆくことになるが、 gosh実行時にそれらをスキャンするのは(特にload-pathが長い場合)結構なオーバヘッドとなる 可能性がある。それは避けたい。スキャンした結果をどこかにキャッシュしておく というのも、キャッシュの一貫性保持が面倒なのでやりたくない。
- 機種依存のバイナリファイルと、機種非依存のschemeファイルは別々の箇所に インストールされている。最近は昔ほど多くないだろうが、異機種がNFSで ディレクトリツリーを共有しているような場合に、後者を共有できるように するためだ。ここで、feature setは機種依存である可能性があるので、 (たとえそれがテキストファイルであっても)前者の方にインストールする 必要がある。
- アンインストールは関連ファイルを消すだけ (いざとなったら手動でも可) という ふうにしておきたいんで、feature set指定ファイルはモジュール本体からなるべく 見つけやすい位置に置いておきたい。
- cond-expandは複数の処理系で使われるコードで、処理系依存部分を 切り替えるのにも使われる (このためにGaucheではgaucheというfeature identifier を定義している)。他の処理系のfeature identifierの衝突はなるべく避けたいので、 gaucheで始まるプレフィックスを一貫して採用したい。
方針。
コンパイル時だけ別のfeature setを使う
Windows/VC++版で、*.scm -> *.c のprecompilationをクロスでやる必要が 出てきたのでなんとかしないとならなくなった。 とりあえず拡張パッケージのことは考えず、メインのGaucheのfeature setは コンパイルインされているものとする。
- 現在はグローバルに(シングルトンで)持っているfeature setをオブジェクト化
- feature setをコピーして、必要なfeatureを足したり引いたりする操作をつける
- compileの環境としてfeature setを渡せるようにする。
- cond-expandの展開はcompileセッションに渡されたfeature setを参照するようにする。
最初のふたつは難しくない。後のふたつはどうするのが良いだろうか。
- feature setをコンパイル時環境に加える (現在のところ、 コンパイル時環境は module(グローバル環境) + レキシカル環境)
- parameterで持ち回る。
- feature identifierをsyntacticな束縛としてグローバル環境に加える。 (「syntacticな束縛」はdefineやlambdaなどコンパイラが認識する束縛ってこと)。
1, 2は通常の束縛とは直交する名前空間を持つことになるので あんまり綺麗じゃない (現状もそうだけど)。 3は名前空間のルールが統一されてすっきりするけど、うっかりユーザモジュール内で feature identifierと同名のグローバル変数を定義してしまうと 元のfeature identiferがシャドウされてしまうとかいう落とし穴があるかも。 (R6RS的に、束縛の衝突を許さないことにすれば安全なんだけど、 影響がでかいしなあ)。 一番簡単なのは2.かなあ。