パッケージに対する雑感
よく他の言語で、コードを読みにくくするので、特定の名前空間を丸ごと取り込む(OCamlのopen MとかHaskellのimport MとかC++のusing Nとか)のは要注意って言われるけど、Common Lispの場合どれだけuse-packageしても、
(symbol-package 'x)
でホームパッケージを確認できるから、気軽にシンボルをインポートできる。これは、識別子(シンボル)がファーストクラスオブジェクトなことによる大きな利点だと思う。運用で回避したり(名前空間を取り込む代わりに、短い別名を付けてアクセスすることが多い)、ツールで補助する必要があまりない。
また、名前空間(パッケージ)自体もファーストクラスなオブジェクトなので、どういうシンボルが含まれるかとか、他から取り込んでいるシンボルはどれかとか、そういうことをコードの字面から読み取る情報としてではなく、計算可能なデータとして扱うことができる。これも大きなアドバンテージじゃないだろうか。Lispの動的、対話的に開発する文化が良く出ている機能だとも思う。
対して、少し扱いづらいと感じる部分もある。例えば、OCamlにはlocal moduleやlocal openという機能があって、
(* ListモジュールにLという別名を付ける *)
let module L = List in L.iter print_string [ "a"; "b"; "c" ]
(* 部分的にListモジュールをopenする *)
let open List in iter print_string [ "a"; "b"; "c" ]
のようなことができるけど、こういう粒度の細かい名前空間の切り替えは、リーダーとパッケージの仕様の都合上、ANSI Common Lispの範囲ではできないと思う。(リーダーマクロを使えば実現できそう)
ちなみに、部分的にuse-packageするのとは少し違うけど、Allegro CLやSBCL(1.0.55以降)には、
ppcre::(split ":" "a:b:c")
のように、指定したパッケージをカレントパッケージにして式を評価する構文がある。ただし、式の中ではパッケージが切り替わっているので、
cl-user::(let ((x 0)) cl::(print x)) ; cl::(...)の中のxの参照はcl:xの参照と解釈される
こういうことはできない。
他にも、シンボルが衝突するなどの理由でパッケージをuse-packageしたくない、でもこの長いパッケージ名を毎回明示するのは厳しい、という場合、
(let ((nicknames (package-nicknames :too-long-package-name)))
(rename-package :too-long-package-name
:too-long-package-name
(cons :short-name nicknames)))
なんて感じで、新しい別名を付けたりすると思う。書いているコードがアプリケーションなら特に問題はないんだけど、それがライブラリだったとき困る。ライブラリをロードするたびに特定のパッケージに別名を付けてしまうのは、いかにも行儀が悪い気がするし、その別名が他のパッケージ名と衝突なんてした暁には、ライブラリの利用者に対処してもらうことになって色々悲しい。
(defparameter *old-nicknames* (package-nicknames :too-long-package-name))
(rename-package :too-long-package-name
:too-long-package-name
(cons :short-name *old-nicknames*))
...
(rename-package :too-long-package-name
:too-long-package-name
*old-nicknames*)
のようにすることはできるけど、これを毎回書くのもどうなんだという気もする。この辺りの事情を改善するためのものがFrancois-Rene Rideauのpackage-renamingだと思うんだけど、READMEを読む限りでは、少しトリッキーな感じで他の人には勧めにくい。
とまあ、物足りなく感じる部分もあるけど、Common Lispのパッケージがとても柔軟で強力な仕組みなのは間違いないと思う。特に、対話的に開発する場合には威力を発揮する。……と、他の言語を使っていて改めて実感した次第。
0 件のコメント:
コメントを投稿