ASDFではソースファイルを扱うためにいちいちコンポーネントとして登録する必要があり、ディレクトリ中のすべてのソースファイルをまとめて扱うという機能がない。これはpackage-inferred-systemを使った場合も同じで、ASDFが.asdファイルから特定のソースファイルに辿りつくためにはどこかでファイル名を指定する必要がある。この点について、ASDFの拡張を考える。
最初にpackage-inferred-systemを使わない場合(例はfoo.asd)を扱い、その後でpackage-inferred-systemの拡張(例はfoo-infer.asd)について検討する。
このようなコンポーネントはmodule
のサブクラスとして簡単に書ける。ただ、foo.asdでの実装はやや単純すぎるか。実用するとしたら、初回のcomponent-children
の呼び出しでchildren
スロットに結果を保持しておき、2回目以降は、既存のcl-source-file
については新しいオブジェクトを作らないようにするべきだろう。
:recursive t
オプションで、直下だけでなく再帰的にソースファイルを収集するというデザインもありかもしれない。component-children
はただのアクセサとして呼ばれるので、本来はO(1)であるべきだろう。2回目以降はディレクトリのタイムスタンプを調べて変化がなかったら何もしないようにするべきかもしれない。その場合は:recursive t
は導入できない。cl-source-module
中のコンポーネントの順番は保証されないということで良さそう。そもそも順序が重要な状況ならそれをファイル名で指定するのは良くないだろうし。:components
に明示的に指定したファイルについては順番を先にする、みたいなルールは考えられる。ただ、「順番を後にする」が必要な場合もありそうだし、cl-source-module
がそれらの面倒を見るべきなのかどうか疑わしい。必要なら以下のようにすれば良いだけだし、そちらのほうが明快だろう。
:serial t
:components ((:file "initial")
(:cl-source-module "bar")
(:file "final"))
asdf/contribの中にwild-moduleというものを発見した。これが答えなのかもしれない。
package-inferred-systemが絡む場合は、デザインが自明でない。foo-infer.asdの例は、同じディレクトリのすべての.lispファイルを再エクスポートしたall.lispを、locate-system
のタイミングでソースリポジトリ内に作るという仕組みになっている。いちいちall.lispを書かなくてもfoo/bar/all
をuse
できるという意図だ。これは使用可能ではあるが、妥協の産物だとも感じる。すぐに思いつく短所は以下の通り:
- ASDFがソースリポジトリを変更することになる。
- 特定の名前(
all
など)を特別扱いしている。 - 実装が良くない。
*system-definition-search-functions*
の使い方が変数の意図とは異なっている。
最初の問題点はアウトプットトランスレーションを使えば解決できるかもしれないが、根本的には、package-inferred-systemに類似の新しいシステムを定義する必要がありそうだと思った。
この際、1パッケージ1ファイルのスタイルを1パッケージ1ファイルまたはディレクトリに変更するというという案がまず思いつく。つまり、サブパッケージfoo/bar
がuse
されたとき、bar.lispが存在せずbar/が存在する場合は、bar/*.lispを再エクスポートしているパッケージとみなす、というルールが基本になる。これは素朴なアイデアだが、問題点が2つある。
- ルールの明快さが損なわれる。
foo/bar
がbar/を指しているかbar.lispを指しているかはファイル構造に依存することになる。 - 十分に強力でない。そもそもこの仕組みは、bar/all.lispのような
:use-reexport
のためだけのファイルを作らなくてもよくするためのものだったはずだが、そのためにはもっと柔軟である必要がある。というのも、foo/bar/all
のように各階層を再エクスポートするパッケージは、さらにその下のfoo/bar/baz/all
も再エクスポートしたりするものだからだ。だからといって、デフォルトでbar/以下の全ての*.lispを再帰的にスキャンして再エクスポートすれば良いかというと、それも不適切な場合がある。(例えば内部関数をbar/internal/に分離している場合など)
最初の問題点については、末尾スラッシュで区別する、つまりfoo/bar
ならbar.lispに対応しfoo/bar/
ならbar/*.lispに対応するという回避策もあるか。ただ、末尾スラッシュのみで重要な識別をするのは人間に優しくないし、普通は「良くないデザイン」とされる気がする。
いっそのこと、最初から*
と**
をワイルドカードとして導入したほうがすっきりするかもしれない。つまり、
- パッケージ
foo/bar/*
はbar/*.lispを再エクスポートしているパッケージである。 - パッケージ
foo/bar/**/*
はbar/**/*.lisp、つまりbar/以下のすべての.lispファイルを再エクスポートしているパッケージである。
UNIXスタイルと共通しているので意味が明らかだし、このルールには、パッケージfoo/A/B
がA/B.lispに対応するという基本原則を崩さずに済むという利点がある。
(最初に思いついた時は、foo/bar/**
をfoo/bar/**/*
の省略として許すべきかと思ったが、一貫性の観点ではあまり良くないように見える。というのも、同様にfoo/bar
もfoo/bar/*
の省略として許されることにすると話が元に戻ってしまうからだ。)
ただ、自動生成されたfoo/bar/*
などのパッケージのpackage-nicknames
をどうするかという問題は残っている。foo/bar/*
のニックネームをデフォルトでfoo/bar
などに決めてしまうとやはり同じ問題が生じるので、ニックネームを必要とするユーザーには基本的にはrename-package
等で後からニックネームをつけてもらうことになりそう。もっとも、自動でニックネームをつける(foo/bar/*
→foo/bar
)オプション自体はあってもよいかもしれない。
→wild-package-inferred-systemとして実現した。