t-cool

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について詳しく知りたい場合、次のリンクを参照してください。