この記事は, Lisp Advent Calendar 2019の9日目の記事です.
この記事のライセンスはCC-BYとします.
この記事では, Common Lispのdefpackageの:shadowオプションとshadowing-import-fromオプションについて扱います.
1日目の記事Common Lispでの開発実例 with package-inferred-systemの捕捉記事的な側面も持ちますので,
宜しければそちらの記事もお読みください.
TL;DR
- シンボルの名前の衝突を避けるために
:shadowオプションを指定できる. - 依存している複数のパッケージ間のシンボルの名前の衝突を避けて, 特定のパッケージのシンボルを使う場合には,
:shadowing-import-fromオプションを使って指定する. - 別のパッケージから受け継いだシンボルについて, 関数定義や変数定義を行うと元のパッケージの関数や変数の再定義となるので注意が必要
defpackage, :export, :use, :import-from
Common Lispでは, packageという物を定義することで, 関数や変数等を集めて, 名前空間を(他のpackage)分離することが出来ます.
Common Lispにはsymbolというものが存在して, これらは他の言語で言うところの変数(変数名)のようなものです. 1
packageは, symbolを他のpackageに提供したり, 他のpackageから提供されるsymbolを自身の中に受け継いだりすることが出来ます.
(defpackage :sample
(:use :cl :foo)
(:import-from :bar :hoge)
(:export :piyo :fuga))
(in-package :sample)
(defun piyo () nil)
(defvar fuga 42)
(defun hogera (a b) (+ a b))
これは, packageの定義の例です. この例では, 最初のdefpackageでSAMPLEというパッケージを定義しています.
:exportオプションの引数に:piyo :fugaとあるのでPIYOとFUGAという名前のシンボルを外部に提供します.
:useオプションの引数には, :cl :fooとあるので, パッケージCLとパッケージFOOが提供するすべてのシンボルを自身(パッケージSMAPLE)の中に受け継ぎます.
例えばパッケージCLは, CARというシンボルを提供していますが, パッケージSAMPLEの中では, carとパッケージ修飾子無しにアクセスできます.
この場合, cl:carやcl::carのようにしてもアクセスすることができます. これらはパッケージ修飾子付きのシンボルと呼ばれます.
:import-fromオプションの引数には, :bar :hogeとあるので, パッケージBARからHOGEというシンボルをパッケージSMAPLEの中に受け継ぎます.
この場合, パッケージSAMPLEの中で, hogeの様にパッケージHOGEはパッケージ修飾子無しにアクセスできます.
もし, パッケージBARがシンボルBAZもexportしていたとすると, パッケージSAMPLEの中で, bar:bazの様にコロンひとつのパッケージ修飾子付きのシンボルで, アクセスできます.
また, bar::bazの様にコロンふたつでもアクセスできます.
ちなみに, パッケージBARの中に, exportされていないQUXというシンボルがあるとするとこちらはbar::quxのようにコロンふたつでアクセスできますが, このアクセスの方法はおすすめ出来ません.
- あるパッケージがエクスポートしているシンボルをすべて受け継ぐ時には,
:useを使います. - あるパッケージから一部のシンボルを受け継ぐ場合, (あるいは一つもシンボルを受け継がない場合)には
:import-fromを使います.
この記事では, あるパッケージ内からパッケージ修飾子なしでアクセスできるようにすることを, シンボルをパッケージに受け継ぐと表現しています.
:shadow
100のシンボルが定義されているパッケージから1つだけシンボルを受け継ぎたい場合には, :import-from :xxx :yyyの様に書けば良いということは前節で書きました.
逆のパターンを考えましょう. 100のシンボルが定義されているパッケージからひとつだけを除いた99のシンボルを受け継ぎたいという場合にはどうするかということです.
この様な場合には:shadowを用います.
この除くひとつのシンボルがZZZだとしましょう. (つまり:zzzと指定されるシンボル)
次のようにdefpackageを書くことで, シンボルZZZは受け継がれません.
(defpackage :shadow-sample
(:use :cl :xxx)
(:shadow :zzz))
(in-package :shadow-sample)
何のために受け継がないのか
ところで, 何故受け継がないという選択をしたいことがあるのでしょうか. 99個もシンボルを受け継ぐわけですから, 1個くらい受け継ごうが受け継がまいが関係なさそうにも思えます.
単純に, xxx:zzzと明示的にアクセスしたいと言うだけであれば, :shadowオプションを指定せずにその様に書いても良いはずです.
(気づかずに使ってしまうことを防げるくらいのことはあるかもしれませんが.)
実はそれ以上の意味があります. 次のコードを見てください.
(defpackage :shadow-sample
(:use :cl :xxx))
(in-package :shadow-sample)
(defun zzz () 0)
先程の例と同じ様に, パッケージXXXがシンボルZZZをエクスポートする状況だとします.
この時の(defun zzz () 0)は, パッケージXXXのZZZを新しくここで定義した関数で再定義するという動作になります.
受け継がれたシンボルに対しては同じ動きをします.
XXXにもSHADOW-SAMPLEにも依存するパッケージがあり, そのパッケージからXXX:ZZZを使うと,
SHADOW-SAMPLEでXXX:ZZZが書き換えられたものが使われます.
(defpackage :xxx
(:use :cl)
(:export :zzz))
(in-package :xxx)
(defun zzz () 42)
元のパッケージXXXで上のコードのようにZZZが定義されていたとしても, パッケージSHADOW-SAMPLEで書き換えられているので, (xxx:zzz)は42ではなく0を返します.
これは困りますね. 無論わざとその様にするということもあるのかもしれませんが, あまりその様にして良いパターンは多くないでしょう.
:shadowオプションを使うとこの様なことがなくなります. zzzをdefunやdefvarなどで関数変数定義に使わなくても,
パッケージSHADOW-SAMPLE内にシンボルZZZが存在することになります.
:shadowオプションで複数のシンボルを指定することも出来ます.
defunやdefvarで使うシンボルZZZを使うとXXX:ZZZのことではなくSHADOW-SAMPLE::ZZZのことを指すことになり, 関数の再定義は起こりません.
衝突を防ぐ
さて, 他のパターンを見てみます. パッケージVVVもシンボルZZZをエクスポートしているとしましょう.
(defpackage :shadowing-sample-2
(:use :cl :xxx :vvv)
(:shadow :zzz))
(in-package :shadowing-sample-2)
XXXとVVVの両方のZZZをパッケージ修飾子無しに使わないで良いのであればこのようにします.
もし, :shadowに:zzzを指定しなければ, XXXとVVVどちらのZZZか分からない, 別々の二つのものを同じ名前の一つのものとして扱おうとすることになり,
名前が衝突したとしてエラーになります.
:shadowing-import-from
直前の例の場合で, XXXのZZZはパッケージ修飾子無しに使いたいとします. この様な場合には:shadowing-import-fromを用います.
(defpackage :shadowing-import-from-sample
(:use :cl :xxx :vvv)
(:shadowing-import-from :xxx :zzz))
(in-package :shadowing-import-from-sample)
この様に:shadowing-import-fromオプションにパッケージ名続けてシンボル名(複数可能)を書くことで, 同名のシンボルを提供するパッケージから一つを選んで, そこからそのシンボルを受け継ぐことが出来ます.
この様にすることで, パッケージXXXとYYYからの(両シンボルZZZによる)の名前の衝突を防ぐとともに, パッケージXXXからはシンボルZZZを受け継いで,
SHADOWING-IMPORT-FROM-SAMPLEパッケージ内で, シンボルXXX:ZZZにパッケージ修飾子なしにアクセスすることが出来ます. 2
まとめ
- シンボルの名前の衝突を避けるために
:shadowオプションを指定できる. - 依存している複数のパッケージ間のシンボルの名前の衝突を避けて, 特定のパッケージのシンボルを使う場合には,
:shadowing-import-fromオプションを使って指定する. - 別のパッケージから受け継いだシンボルについて, 関数定義や変数定義を行うと元のパッケージの関数や変数の再定義となるので注意が必要