Lambdaカクテル

Common Lispと自宅サーバにWebエンジニアリングの香りを載せて

Common Lisp困りどころ その1: `package-inferred-system`

Common Lispは良い言語だが,初心者が必ず陥る困りどころがいくつかあり,これで出鼻を挫かれるようなことがあってはいけないと思っている. そこで,この困りどころを提示することによって,よりよい解決方法や回避策がインターネットを通じて集ってくるのをうながそうという試みとして,この「 #CommonLisp困りどころ 」シリーズをやることにした.

twitter.com


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/barsrc/foo/bar.lispを参照してくれるようにならないだろうか?

・・・といった話をtwitterでしていると,親切な方がいろいろ教えてくれた.残念ながら,実践してみてもうまくいかなかった.どこに指定すればよかったのだろう.

なんとかモダンなCommon Lisp開発環境を手にしたいので,誰か助けてほしい!!!

Related entries

ASDF - Another System Definition Facility

blog.3qe.us

octahedron.hatenablog.jp

qiita.com

require, ASDF, quicklispを正しく使う | κeenのHappy Hacκing Blog

*1:SBCL,CCLといったネイティブコンパイラの場合の話.別の処理系ではまた事情が異なるかもしれない.

*2:このあたりの理解はあいまいなので,間違いや補足があれば指摘してほしい