package-inferred-systemに期待したもの
これまでのあらすじ
package-inferred-systemは、pakageのinferred-systemである。
なんだか体がだるい感じのぼくはこころに学びを沁みわたらせるため、ライブラリのパッケージをすっきりと書くことはできないか考えてみようと思った。Common Lispを書いていてディレクトリ構成とパッケージ構造を同じにしたいとき、以下の2点が面倒だ:
- パッケージ名をわざわざパッケージ定義に書くこと
- パッケージ名とディレクトリ名を一致させること
- パッケージ構造を変えたとき、変更が漏れやすい
そこでぼくは、1ファイル1パッケージのスタイルでプログラミングできるらしいpackage-inferred-systemを試してみようと考えたのだった──
TL;DR
package-inferred-systemは、ディレクトリ構造からパッケージ名を決定するためのものではない。
package-inferred-system とは
package-inferred-systemは、Common LispのASDFの拡張機能です。ASDFはCommon Lispにおいてライブラリ定義・読み込みを司る、ほぼ標準のアドオンです。Pythonでいえば、setup.py
(定義)とdistutilsに相当します。それにしてもPythonにおいて。「ほぼ標準」というのは、Common LispはANSIで言語仕様が定められていますが、ASDFはその中には含まれておらず、しかしだいたいの処理系に始めからバンドルされているので準標準的である、という意味です。モジュール
とパッケージ
は別のものを指す言葉だったのかー*1、知らなかったぜ 😎
ASDFの公式ドキュメントに拠れば、package-inferred-systemとは
Starting with release 3.1.2, ASDF supports a one-package-per-file style of programming, whereby each file is its own system, and dependencies are deduced from the defpackage form (or its variant uiop:define-package).
ASDF Manual: The package-inferred-system extension
とのことです。この機能に対して、誤った期待をしていたために、いろいろもやもやさせられた、というのがこの記事の要旨です。
期待していたもの
ぼくがone-package-per-file
という言葉で期待したのは、Pythonのモジュールシステムのようなものでした。つまり、次に述べるようなものです。
Pythonでは、ソースコードのファイル自体がモジュールです。なので、以下のようなa.py
とb.py
があるディレクトリでインタプリタを立ち上げて
# a.py s = 's in module a'
# b.py s = 'string in module b'
>>> import a >>> a.s 's in module a' >>> import b >>> b.s 'string in module b'
ということができるようになる、と思っていました。つまり、Common Lispで次のようなことができるようになるのだ、と。
;;;; a/hoge.lisp (defun foo () (format t "No defpackage!!~%"))
CL-USER> (use-package :a/hoge) CL-USER> (a/hoge:foo) "No defpackage!!
いや、export
してないじゃん、という意見はごもっともですが。export
は手でする必要はあるにしろ、ディレクトリ構造と一致したdefpackage
を書かなくなるなら、それは楽だなあと思っていました。
実際のpackage-inferred-system
じゃあ、実際にはどうだったか。
こうでした:ファイルパスと一致したdefpackage
をしておけば、ASDF側でディレクトリ構造からパッケージ名を想像して読み込んでくれる。
つまり、冒頭に挙げた問題点
- パッケージ名をわざわざパッケージ定義に書くこと
- パッケージ名とディレクトリ名を一致させること
は解決してくれません!! 残念!!!
ただ、便利な点がまったくないかというとそういうことでもありません。以前ならば.asd
フィアルに書いていた依存関係を、package-inferred-systemが内挿してくれるために、書かなくてよくなります。たとえばpackage-inferred-systemでない以下のようなsystemがあったときに、
;; hoge.asd (defsystem hoge :depends-on (:alexandria) :components ((:module "src" :components (:file "fuga") (:file "core") (:file "hoge" :depends-on ("fuga" "core"))))
以下のように書くことで、:components
節がまるまる不要になります。
;; hoge.asd (package-inferred-system) (defsystem foo :depends-on ("alexandria" "hoge/hoge"))
ただし、各パッケージに該当するファイルには、それぞれ以下のようなdefpackageをする必要があります(長くなるのでin-package
は省略しました)。
;; hoge/hoge.lisp (defpackage :hoge/hoge (:use :cl :hoge/src/core :hoge/src/fuga))
(defpackage :hoge/src/fuga (:use :cl))
;; foo/src/core.lisp (defpackage :hoge/src/core (:use :cl))
つまり、ディレクトリ構造と:use
などからパッケージの依存関係を推測してくれるんだよ…!!
おわりに
package-inferred-systemはone-pakcage-one-fileのスタイルを支援する機能です。しかしそれは、パッケージ名を自動で内挿してくれる機能ではありません。ディレクトリ構造とインポートの記述からパッケージ依存関係を推測してくれる機能です。
この機能の恩恵を特に得やすいのは、パッケージ数(=ファイル数)が多く、ディレクトリ構造が複雑なプロジェクトです。そういったプロジェクトは.asd
ファイルとディレクトリ構造を手動で一致させる手間が増えるため、defpackageさえちゃんとしていれば、依存関係を書かなくていいpackage-inferred-systemはかなり有用でしょう。
こうして、package-inferred-systemへの溜飲が下がり、これからは使ってみようかなあという気になったのであった。