Common Lispは良い言語だが,初心者が必ず陥る困りどころがいくつかあり,これで出鼻を挫かれるようなことがあってはいけないと思っている. そこで,この困りどころを提示することによって,よりよい解決方法や回避策がインターネットを通じて集ってくるのをうながそうという試みとして,この「 #CommonLisp困りどころ 」シリーズをやることにした.
Common Lispのパッケージ管理における革新であるpackage-inferred-system
について,ちょっとした解説と,不満点を書き出してみる.
もしよければ手をさしのべてほしい.
パッケージの導入
まず一般的なプログラミングの技法として,開発しているソフトウェアが複雑になった場合,モジュールとしてソフトウェアを分割するのが普通である.Common Lispにおいて,モジュールの単位はパッケージpackageである. パッケージに分離させた上で,パッケージが別のパッケージのシンボルをインポートしたり,自分のパッケージのシンボルをエクスポートしたりする. そして通常ならばファイルもこれに応じて分離するだろう.
ASDFにおけるコンパイル
ところでCommon Lispで標準的なビルドツールであるASDFを使ってプロジェクト(ASDFの用語ではシステムsystem)を実行しようとするとき,基本的にまずプロジェクトを構成するファイル同士の依存関係が解決され,その後で全てのファイルが一定の順序にコンパイルされていく. つまり動作時に動的にファイルがコンパイルされ読み込まれるということが基本的にない.実行時には全てのパッケージがコンパイルされている必要がある*1わけだ.このあたりはPythonなどのスクリプト言語とは趣が違うところだ. 関数の類を呼び出す際に存在しているかどうかが確認されるのではなく,コンパイル時点で参照できなければならないのだ*2.
コンパイルを正常に行うためには,依存関係を記述する必要がある
つまり,間違った順序でソースコードをコンパイルすると,コンパイルに失敗する.必ず正しく依存関係に適った順序でコンパイルを行う必要がある.
したがって,コンパイルをシステマチックに行うため,開発者はファイル(パッケージ)同士の依存関係をプロジェクトトップに配置されたプロジェクト管理用のファイルプロジェクト名.asd
(ASDファイル)に記述する必要がある.
依存関係記述は面倒だが,ASDFはそれをやらない
ASDFにおける依存関係の記述はとにかく面倒な代物で,構成ファイル数が片手の指に入る間はどうということないのだが,ファイル数が増えるにつれてファイル間の依存関係が爆発し,人間には制御できない代物になることは想像に難くない.こんなことは人間のすることではない.だがASDFはこれを自動的には行ってくれないのだ.なぜなのか理由を考えてみよう.
解決不可能なのはなぜか
ファイル同士の依存関係が解決できないというのを言い換えると,ファイルをコンパイルするために別のどのファイルがコンパイルされているべきかが分からない,ということである. ファイルにはいくつかのパッケージがあり,それらは別のパッケージに依存している.だがパッケージ名からファイルに至るパスを推測(infer)できないので,そのパッケージがどのファイルにあるかは分からないのである.
one package one file same nameの導入
この問題,ファイル同士の依存関係を自動的に解決できない問題は,ファイル名とパッケージ名を統合し,1ファイル1パッケージとする規約を導入することで解消することができる.
パッケージ名からファイル名を推測することができるようになるからである.そして必然的に1ファイルには複数パッケージを置くことができなくなる.
One package, One file, Same nameである.これを実現するのがASDFの拡張機能であるpackage-inferred-system
である.直訳するとパッケージ推測型システムといったところか.
package-inferred-system
では,sampleproject
というASDFプロジェクトがあるとき,foo/bar
というパッケージをuse-package
などで読み込んでいると,ASDFはsampleproject.asd
があるディレクトリを起点として./foo/bar.lisp
を参照してくれる.
不満点
この仕組みはいたってよくできていて,複数のパッケージからなるシステムを書く際の負担をとても軽くしてくれる.ファイル名を考えなくていいところがとてもモダンに感じられる. ただ不満な点があり,筆者はこれを解消できていないため,識者のアドバイスを求めたいと思う.それは・・・
ファイル名解決の起点となるパスを変更できないか?ということである.つまりこういうことである.
現代的なプロジェクトでは,実際的にはソースコードはsrc/
といったサブディレクトリに押し込められ,これに並んでscripts/
やdocuments/
といったサブディレクトリが並ぶことが多い.この場合でも,sampleproject.asd
といったASDファイルをプロジェクトルートに置くほうが都合が良いこともある.
例えばテストをすることを考えると,テスト用ASDファイルから本体システムを呼び出すには,両者のASDファイルが同じ場所にあるほうが読み込みの都合が良い.
この場合,package-inferred-system
ではファイル名とパッケージ名が統合され,ファイルはASDファイルを起点として推測されるので,src/foo/bar.lisp
のパッケージ名はsrc/foo/bar
となってしまうのである!こんなのありえない!ださすぎる!
これを回避するためにASDファイルを移動するという手段は前述の理由により避けたい.ファイル名を推測するための起点となるディレクトリを./src
にして,foo/bar
でsrc/foo/bar.lisp
を参照してくれるようにならないだろうか?
・・・といった話をtwitterでしていると,親切な方がいろいろ教えてくれた.残念ながら,実践してみてもうまくいかなかった.どこに指定すればよかったのだろう.
Just add a :pathname "src" to your system definition.
— svetlyak40wt (@svetlyak40wt) 2018年5月30日
なんとかモダンなCommon Lisp開発環境を手にしたいので,誰か助けてほしい!!!