パッケージ

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

barmineにインポートすることで,さらに一歩深く進み, 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にはグローバルな値はなく,そのためエラーが出た. しかしインターンは,名前を打ち込んだ結果として行われていた. よって今foomineにインポートしようとしたとき, すでに同じ名前のシンボルがあったことになる.

あるパッケージを別のパッケージで使うと定義することでシンボルを一気にインポートできる.

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-packagedefpackageを置けば十分だ. (マクロ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個のシンボルをエクスポートしているだけだ. すなわちwinlosedrawだ. 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なので, barmine: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章を参照のこと.


←: オブジェクト指向Lisp     ↑: On Lisp    

Copyright (c) 2003-2011 野田 開     NODA Kai <nodakai@gmail.com>