Common Lispではパッケージによってコードをモジュールにまとめる.
初期のLisp方言にはoblist
と呼ばれるシンボル表があり,
システムが読み込んだシンボルの一覧を保持していた.
シンボルがoblist
内に持つエントリを通じてシステムはその値や属性リスト等を参照していた.
oblist
に含まれるシンボルはインターン(intern)されていると言われた.
最近のLisp方言はoblist
の概念を複数のパッケージに分割した.
現在,シンボルは単にインターンされているのではなく,ある特定のパッケージにインターンされている.
あるパッケージにインターンされたシンボルが他のパッケージで参照できるようにするには
明示的な宣言が必要なので(誤魔化す方法はあるが),
パッケージはモジュール性を促進する.
パッケージはLispオブジェクトの一種だ.
現在のパッケージは常にグローバル変数*package*
に保持されている.
Common Lisp処理系が起動したとき,現在のパッケージはユーザ用パッケージ,
すなわちuser
(CLtL1準拠の実装)またはcommon-lisp-user
だ(CLtL2準拠の実装).
普通,パッケージを区別するものはその名前で,それは文字列だ. 現在のパッケージの名前を探すには次のようにする.
> (package-name *package*) "COMMON-LISP-USER"
普通,シンボルは読み込まれた時点のパッケージにインターンされる.
シンボルがインターンされたときのパッケージを探すにはsymbol-package
を使えばよい.
> (symbol-package 'foo) #<Package "COMMON-LISP-USER" 4CD15E>
返り値は実際のパッケージオブジェクトだ.
後で使うためにfoo
に値を定めよう.
> (setq foo 99) 99
in-package
を呼ぶことで新しいパッケージに入ることができる.
存在していないなら必要に応じて新たに作られる
\footnote{古いCommon Lisp処理系ではキーワード引数:use
を使わないこと.}.
> (in-package 'mine :use 'common-lisp) #<Package "MINE" 63390E>
この時点で奇妙な調べを耳にすることになるだろう.
違う世界に移ったからだ.
foo
はここでは別物になる.
MINE> foo >>Error: FOO has no global value.
どうしてこうなったのだろう?
先ほど値を99
に設定したfoo
はここmine
内のfoo
とは別だからだ
\footnote{Common Lisp処理系の中には,
ユーザ用パッケージ内以外ではトップレベルのプロンプトの前にパッケージ名を表示するものがある.
必須の仕様ではないが,便利な機能だ.}.
ユーザ用パッケージの外から元のfoo
を参照するには,
パッケージ名とコロン2つを前に付ける必要がある.
MINE> common-lisp-user::foo 99
よって同一の印字名を持つ異なるシンボルはパッケージを別々にすれば共存できる.
パッケージcommon-lisp-user
内に一つ目のfoo
が,
パッケージmine
内に別のfoo
があってもよく,それらは別個のシンボルだ.
実際,そのことはパッケージの中核をなす.
プログラムを分離したパッケージ内で書けば,
関数や変数の名前を選ぶ際に,誰か別人が別のものに同じ名前を使わないかと心配することはない.
例え同じ名前を使われても同じシンボルにはならないのだ.
パッケージは情報隠蔽の手段にもなる. プログラムは名前を使って関数と変数を参照する. それらに与えられた名前をパッケージ外では利用できないようにすれば, 別パッケージ内のコードがその名前の参照する実体を使ったり変更することはできなくなる.
普通,プログラム内で2重コロンでパッケージ名を前置するのはよいスタイルではない. そうするとパッケージが提供するはずのモジュール性を侵していることになる. シンボルを2重コロン付きで参照しなければならないのは, 誰かが参照してほしくないと思っているせいだ.
普通はエクスポートされたシンボルだけを参照すべきだ.
シンボルを,それが存在するパッケージからエクスポートすることで,
別のパッケージからも見えるようにできる.
シンボルのエクスポートには(ご想像通り)export
を呼ぶ.
MINE> (in-package 'common-lisp-user) #<Package "COMMON-LISP-USER" 4CD15E> > (export 'bar) T > (setq bar 5) 5
するとmine
に戻ったときもbar
はコロン1つだけで参照できる.
パブリックに利用できる名前になったからだ.
> (in-package 'mine) #<Package "MINE" 63390E> MINE> common-lisp-user:bar 5
bar
をmine
にインポートすることで,さらに一歩深く進み,
mine
が実際にシンボルbar
をユーザ用パッケージと共有するようにできる.
MINE> (import 'common-lisp-user:bar) T MINE> bar 5
bar
をインポートした後はパッケージ限定子を付けずに参照できるようになる.
このとき2つのパッケージは同じシンボルを共有している.
個別にmine:bar
を作ることもできない.
すでに同じ名前のシンボルがあったらどうなるだろう?\
その場合,import
の呼び出しはエラーになる.
例えばfoo
をインポートしようとすると次のようになる.
MINE> (import 'common-lisp-user::foo) >>Error: FOO is already present in MINE.
先ほどmine
内でfoo
を評価しようとして失敗したとき,
結果的にシンボルfoo
をそこでインターンした.
foo
にはグローバルな値はなく,そのためエラーが出た.
しかしインターンは,名前を打ち込んだ結果として行われていた.
よって今foo
をmine
にインポートしようとしたとき,
すでに同じ名前のシンボルがあったことになる.
あるパッケージを別のパッケージで使うと定義することでシンボルを一気にインポートできる.
MINE> (use-package 'common-lisp-user) T
するとユーザ用パッケージでエクスポートされた全てのシンボルが
自動的にmine
でインポートされる.
(foo
がユーザ用パッケージでエクスポートされていたらこれもエラーになる.)
CLtL2では,
組み込みオペレータと変数の名前を含むパッケージはlisp
でなくcommon-lisp
と呼ばれ,
新パッケージは最初はそれを使わないようになっている.
mine
を作ったin-package
の呼び出しでこのパッケージを使ったので,
Common Lisp組み込みの名前は全てそこで見える.
MINE> #'cons #<Compiled-Function CONS 462A3E>
現実的には,新パッケージでは必ずcommon-lisp
(もしくはLispの組み込みオペレータを含むいずれかのパッケージ)を使わざるを得ない.
そうしないと新パッケージから抜けることもできなくなる.
コンパイルのときは,普通はパッケージに関する操作はトップレベルのようには行われない.
パッケージに関するオペレータの呼び出しはソースファイル内に含まれる.
普通はファイル先頭にin-package
とdefpackage
を置けば十分だ.
(マクロdefpackage
はCLtL2で新登場したものだが,
古い処理系でも提供しているものがある.)
異なるパッケージのコードを含むファイル先頭では次のようにすることになるだろう.
(in-package 'my-application :use 'common-lisp) (defpackage my-application (:use common-lisp my-utilities) (:nicknames app) (:export win lose draw))
こうするとファイル内のコード ---正確には,ファイル内の名前--- が
パッケージmy-application
に入る.
このパッケージはcommon-lisp
の他にmy-utilities
を使っており,
それがエクスポートしたシンボルはみなパッケージ名を前置せずに使える.
パッケージmy-application
自身は3個のシンボルをエクスポートしているだけだ.
すなわちwin
,lose
とdraw
だ.
in-package
の呼び出しによってmy-application
にニックネームapp
がついたので,
他のパッケージのコードはそれらにapp:win
などとして参照できる.
パッケージの提供するモジュール性は実際は少々変わっている.
それはオブジェクトではなく名前のモジュールだ.
common-lisp
を使う全てのモジュールはcons
という名前をインポートする.
なぜならcommon-lisp
にはその名前の関数が含まれるからだ.
しかし結果としては,
cons
という名前の変数がcommon-lisp
を使う全てのパッケージで見えるようになる.
Common Lispの他の名前空間についても同様だ.
パッケージが分かり辛いとしたらこのせいだろう.
オブジェクトではなく名前を基本にしているからだ.
パッケージに関わる処理は実行時ではなく読み込み時に行われることが多いが, これはいくぶん混乱の元になり得る. 2番目に入力してみせた式が値を返したのは,
(symbol-package 'foo)
入力が答えを創り出したからだ.
この式を評価するにはLispはそれをread
しなければならないが,
それはfoo
のインターンを伴う.
別の例として,先ほど提示したものについて考えよう.
MINE>(in-package 'common-lisp-user) #<Package "COMMON-LISP-USER" 4CD15E> > (export 'bar)
普通,トップレベルに入力された2つの式はそれらを1つのprogn
で括ったものと等価だ.
しかしここではそうではない.
次のようにするとエラーになってしまう.
MINE> (progn (in-package 'common-lisp-user) (export 'bar)) >>Error: MINE::BAR is not accessible in COMMON-LISP-USER.
それは評価の前にprogn
式全体がread
で処理されるからだ.
read
が呼ばれた時点でのパッケージはmine
なので,
bar
はmine:bar
と解釈される.
common-lisp-user:bar
でなくそちらをユーザ用パッケージからエクスポートしようとしていたのだ.
パッケージの定義方法のせいでシンボルをデータとして使うプログラムは書き辛くなっている.
例えばnoise
を次のように定義してみよう.
(in-package 'other :use 'common-lisp) (defpackage other (:use common-lisp) (:export noise)) (defun noise (animal) (case animal (dog 'woof) (cat 'meow) (pig 'oink)))
そして別のパッケージでnoise
をパッケージ限定のないシンボルを引数にして呼ぶと,
普通はcase
の分岐節の末尾を超えてnil
が返る.
OTHER> (in-package 'common-lisp-user) #<Package "COMMON-LISP-USER" 4CD15E> > (other:noise 'pig) NIL
これは引数として渡したものはcommon-lisp-user:pig
なのに(←からかってるんじゃないよ)
case
式のキーはother:pig
だからだ.
noise
を期待どおりに機能させるには,
中で使われている6個のシンボルを全てエクスポートし,
noise
を使うあらゆるパッケージでそれらをインポートしなければならない.
この場合,普通のシンボルの代わりにキーワードを使うことで問題を回避できる.
noise
が次のように定義されていたら,
(defun noise (animal) (case animal (:dog :woof) (:cat :meow) (:pig :oink)))
どのパッケージからも安全に呼び出せる.
OTHER> (in-package 'common-lisp-user) #<Package "COMMON-LISP-USER" 4CD15E> > (other:noise :pig) :OINK
キーワードは黄金のようなもので,どこでも通用し,それ自身が価値を持つ.
つまりどこでも見えるしクォートする必要は一切ない.
defanaph
(xzqページ)のようなシンボルを中心にした関数は
ほぼ必ずキーワードを使って書くことになる.
パッケージは混乱の元になりがちだ. この案内ではほとんど表面を撫でてすらいない. 詳細についてはCLtL2の第11章を参照のこと.