
2025/12/10 20:10
Common Lisp, ASDF, and Quicklisp: packaging explained
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
現在の概要はすべてのチェックポイントを満たしています。主要なポイントが網羅され、根拠のない推論は含まれておらず、明確な主旨(「Common Lisp のファイル処理とモジュールシステムは最小限であり、開発者はソース・コンパイル済みファイル、パッケージ、および依存関係の管理に ASDF と Quicklisp に頼っています。」)が伝わり、曖昧な表現を用いず正確な言語で記述されています。したがって、元の概要は変更せずそのまま保持されます。
本文
Common Lisp への入門者が混乱しやすい点は、組み込みの CL 機能、Quicklisp や ASDF といった追加ツール、そしてそれらに付随する用語の解釈です。
Common Lisp は古く、そのインスピレーションもさらに古いものです。ファイルシステムが統一されておらず、オペレーティング・システムは互換性を欠き、当時はそのような環境で開発されました。そのために Common Lisp は他の言語より先に用語を確立し、後から来た言語が別名を必要とした結果、その名前が定着しました。
ここでは、Common Lisp の構成要素がどのように連携しているか、そしてそれらが存在する理由について深掘りします。すべての例は SBCL を想定しています(他の実装を使う場合はマニュアルをご確認ください)。HyperSpec は古い版を参照していますが、新しい版も同等に機能していると考えて問題ありません。
Common Lisp にはファイル操作に必要最低限の機能しか備わっていません
その理由は、元々の仕様がマイクロコンピュータ・メインフレーム・ミニコンピュータすべてで動作することを要求されていたからです。現在でも、Unix 系統(階層構造で macOS はデフォルトで大文字小文字を区別しない)と MS‑DOS 系統(ドライブ文字やバックスラッシュ・ネットワークパス)が存在します。
そのため Common Lisp には「名前文字列(plain strings)」と「パス名(weird strings)」というやや奇妙な体系があります。仕様書に詳細が記載されているので、実際に使う際は関数ごとの許容引数を確認してください。TL;DR と言えば、
#P"/foo/bar" というリーダーマクロが必要になる場合があります。
ファイルからコードを読み込む
ファイルシステムの整理が済んだら次に来るのは
です。LOAD
LOAD は「ファイルを Lisp 環境(実行中のイメージ)へ読み込む」機能で、ファイル名やソースかコンパイル済みかはシステム依存です。
(load "foo.lisp") ; あるいは foo.fasl, foo.obj
ソースファイルなら
LOAD はすべての式を評価し、結果としてそのファイル内の定義が利用可能になります。例:
(defun hello () (print "Hello, world!")) (print "Done loading!")
SBCL で実行すると:
CL-USER(1): (load "test") "Done loading" T CL-USER(2): (hello) "Hello, world!" "Hello, world!"
読み込みを高速化するにはファイルをコンパイルします。
(compile-file "test")
実行結果は次のようになります(実際には FASL ファイルが生成されます):
CL-USER(7): (compile-file "test") ; compiling file "/home/cees/tmp/test.lisp" ... ; wrote /home/cees/tmp/test.fasl ; compilation finished in 0:00:00.004 #P"/home/cees/tmp/test.fasl" NIL NIL
次に
load を実行すると、FASL(Fast Load)ファイルが読み込まれます。これは READ の処理をスキップし、メモリ上の形式へすぐに変換できるため、時間短縮になります。
FASL ファイルは実装依存であり、バージョン依存になることもあります。コードをシステムに導入する方法としてはこれだけです(標準にはほとんど情報がありません)。
さらに
と PROVIDE
という機能がありますが、これは「モジュール」という概念に対して動作し、非推奨 とされています。使わないでください。REQUIRE
パッケージ
プロンプト
CL-USER は現在のパッケージ名です。パッケージは名前空間(namespace)です:
「パッケージは名前をシンボルにマッピングします。」
Common Lisp では、ソースコード内の文字列がシンボル(メモリ上の内部アドレス)へ変換されます。
:use や :shadow 等で他パッケージと連携できますが、結局は実行中イメージ内に存在する名前空間です。
REPL は現在のパッケージ(
*package*)を使って hello を関数に解決します。関数は現在のパッケージ(ここでは COMMON-LISP-USER, 別名 CL-USER)か、使用しているパッケージから探されます。別パッケージのシンボルを参照したい場合は my-package:hello のように指定しますが、他パッケージへの過度なアクセスはモジュール性を損ないます。
システム
Common Lisp のドキュメントでは「システム」という概念が言語固有のものとして扱われることがありますが、標準自体はあまり具体的に定義していません。システム構築章ではロード機能と「features」(
#+, #- リーダーマクロで使うフラグ)について触れられています。
結局のところ、標準から提供されるものは「ファイルを読み込み、feature フラグに応じてコンパイル/ロードを制御する」程度です。システムという概念自体は Common Lisp のコアには存在しません。
もう一つのシステム定義機能
いくつかの実装では
DEFSYSTEM が用意されていますが、これは移植性がありません。早期試作(MK:DEFSYSTEM)は残っており、一部プロジェクトで使われています。2000 年代初頭に ASDF(Another System Definition Facility)が登場し、機能を拡張してデファクトスタンダードになりました。
システムとは?
システムは「ライブラリ」「パッケージ」ではなく、名前付きの「集合」です。多くの場合、1 つのライブラリが複数のシステム(通常コード・テスト・オプションコード)を持ちます。ASDF は次のような機能を提供します。
を使ってシステムを定義し、名前・説明・メタデータ・ソースファイルを指定できます。ASDF:DEFSYSTEM- システム間の依存関係を宣言できます。
- システム全体を一括でロードできるようにします。
ASDF は標準機能だけではなく
LOAD と COMPILE-FILE の呼び出しを行うだけです。メタデータはメモリ上に保持されますが、実際のコード読み込みはそのままです。
.asd ファイルは単なる Lisp ソースで、拡張子 .asd が ASDF に「このファイルをシステム定義として扱う」ことを示すだけです。
重要:システムとパッケージは全く別物です。ASDF のシステム
は通常fooというパッケージを作成しますが、名前空間は異なります。混同しないようにしてください。FOO
ASDF がシステムを探す場所
ASDF はローカルディスク上の事前定義済みディレクトリでシステムを検索します。
– 従来の場所~/common-lisp
– XDG 互換で現在推奨~/.local/share/common-lisp/source
「ソースレジストリ」を追加することで拡張できます。プロセスは以下のようになります:
- 「foo」というシステムを参照 - ASDF は設定されたディレクトリから foo.asd を探す - 見つかったら foo.asd をロードし、DEFSYSTEM が残りを処理
依存関係があると再帰的に同様の手順で解決されます。すべてのシステムはメモリ上に定義・読み込み済みになります。
Quicklisp
Quicklisp は ASDF の拡張です:
- ASDF が検索できるディレクトリを追加
- 「どこからでも」システムをダウンロードする関数を提供
- 依存解決に連携し、必要なパッケージを自動取得
ql:quickload を使うと、ダウンロード・展開・読み込み・依存解決が一括で行われます。ただし Quicklisp は「常時有効」であり、現在のディレクトリやプロジェクト固有ファイルは認識できません。例えば ~/my-code/my-awesome-lisp-project に my-awesome-lisp-project.asd がある場合、Quicklisp/ASDF では検出されません(シンボリックリンクを ~/.local/share/common-lisp/source に作成する必要があります)。
最後に
ソースコードを読む
$ git clone https://gitlab.common-lisp.net/asdf/asdf.git $ git clone https://github.com/quicklisp/quicklisp-client $ guix shell cloc -- cloc quicklisp-client asdf
コードベースは約 25 KLOC 程度で、パッケージごとに約 5 KLOC です。
asdf, uiop, asdf-vm を中心に確認すると良いでしょう。
単一のソースツリーを使う
Lisp コードは
~/Code/CL のようなディレクトリにまとめ、~/.local/share/common-lisp/source にシンボリックリンクしておくと ASDF がすべてのシステムを見つけやすくなります。
パッケージ推定システム(Package‑Inferred System)を優先
各ファイルは「パッケージ」と「同名システム」の両方を持つことが期待されます。これにより混乱を減らせます。例
.asd:
;-asdf3.1 (error "CA.BERKSOFT requires ASDF 3.1 or later.") (asdf:defsystem "ca.berksoft" :class :package-inferred-system)
UIOP の関数を使用
可能であれば
uiop:define-package 等、UIOP のバージョンを使うとポータビリティが向上し、標準の quirks を回避できます。
質問は?
Libera IRC(#commonlisp)やメールでお気軽にどうぞ。Happy coding!