package-inferred-systemでモダンなCommon Lispライブラリを書く
この記事は、David Vázquezさんの記事 [How to write a modern Lisp library with ASDF3 and Package Inferred System] (http://davazp.net/2014/11/26/modern-library-with-asdf-and-package-inferred-system.html)の日本語訳です。Davidさんに許可をいただき、日本語訳を共有させていただけることになりました。
はじめに
最近よく見かけるパッケージの使い方は、package.lispかpackages.lispを加える手法です。多くの場合、このファイル(package.lispかpackages.lisp)は、最初に読み込まれるファイルで、他のファイルが使うパッケージを定義します。この手法は、多くのプロジェクトで用いられています。しかし、パッケージ群が大きくなるにつれて、複数のファイル間での依存関係を管理する規則が必要になります。
パッケージ群を定義するための代替策として、1ファイル1パッケージ(one package per file)と名づけられた手法があります。その名前が示すように、この手法では、全てのファイルはdefpackageで始まります。複数のパッケージ間の依存関係が明示的で、全てのファイルが固有のパッケージ名をもつので、ファイル間の依存関係は推測(inferred)されます。
この手法は数年前にfaslpathとquick-buildによって導入されました。しかし近年、事実上の標準Common LispビルドシステムであるASDF3が、asdf-package-systemの拡張でこの手法をサポートしました。その結果、今日、この手法を使うことはより簡単になりました。
では、少しの間、記事を一緒に読み進めながら、あなたのプロジェクトで、この手法を使う手法を学びましょう。お役に立てれば嬉しいです。
使い方
まず初めに、asdf-package-system拡張を、あなたのシステムで有効にしなければいけません。projectという名前でシステムを定義することから始めましょう。
(asdf:defsystem :project :name "My Project" :version "0.0.1" :class :package-inferred-system :defsystem-depends-on (:asdf-package-system) :depends-on (:project/addons))
ここでは、システム、パッケージ、ファイルの間での一連の流れを定義しています。project/foo/barというシステムは、foo/bar.lispを参照します。foo/bar.lispは、project/foo/barパッケージを提供しなければいけません。useされてimportされるproject/foo/barパッケージは、同じ名前のシステムを参照します。
例えば、projectというシステムは、project/addonsに依存しているので、addons.lispが先に読み込まれなければいけません。addons.lispの内容は、次のように始まります。
(defpackage :project/addons (:use :common-lisp :project/core) (:import-from :cl-ppcre)) (in-package :project/addons) (cl-ppcre:scan-to-strings "h.*o" (hello))
project/coreパッケージをuseしていて、cl-ppcreパッケージを”import”していることに注意してください。よって、ASDFは、それがcl-ppcreとproject/coreの両システムに依存していると推測します。project/coreシステムは、core.lispを参照することを覚えておいてください。core.lispの内容は、次の通りです。
(defpackage :project/core (:use :common-lisp) (:export #:hello)) (in-package :project/core) (defun hello () "Hello!")
たったこれだけです。このファイルは、外部の依存関係を一切もちません。もし、次のシステムを読み込もうとした場合を考えてみましょう。
(asdf:load-system "project")
このシステムと、ファイル群(core.lisp, cl-ppcre, addons.lisp)が、適切な順番でよみこまれます。
別システムとの統合
では、あるパッケージをuseするとして、パッケージを提供(provide)するシステムが、同じ名前でない場合はどうでしょうか。例えば、closer-mopというシステムは、c2clというパッケージを提供(provide)します。ASDFがパッケージのためのシステムを見つけられるように、register-system-packagesという関数を呼び出しますが、その際はシステム名とそのパッケージ群を引数に含めます。project.asdの中に、次の一行を加えます。
(register-system-packages :closer-mop '(:c2cl))
最後のトリック
多くの場合、サブパッケージから全てのシンボルと一緒に1つのパッケージをエクスポート(export)したいでしょう。これは、ASDFに含まれるUIOPを使うことで解決します。例えば、all.lispという名前でファイルを定義してみましょう。
(uiop/package:define-package :project/all (:nickname :project) (:use-reexport :project/core :project/addons))
project/allシステムは、project/coreとproject/addonsの両システムに依存し、project パッケージに、そのシンボルを再度エクスポートします。
より詳しい情報
ASDF3やpackage-systemについて詳しく知りたい場合、次のリンクを参照してください。
- ASDF Manual. The package-inferred-system extension
- [ASDF 3, or Why Lisp is Now an Acceptable Scripting Language (Extended version) by François-René Rideau] (http://fare.tunes.org/files/asdf3/asdf3-els2014.html)