この記事は, 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
オプションを使って指定する. - 別のパッケージから受け継いだシンボルについて, 関数定義や変数定義を行うと元のパッケージの関数や変数の再定義となるので注意が必要