LoginSignup
7
2

More than 3 years have passed since last update.

defpackageの:shadowと:shadowing-import-from

Last updated at Posted at 2019-12-08

この記事は, 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を自身の中に受け継いだりすることが出来ます.

sample.lisp
(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の定義の例です. この例では, 最初のdefpackageSAMPLEというパッケージを定義しています.

:exportオプションの引数に:piyo :fugaとあるのでPIYOFUGAという名前のシンボルを外部に提供します.

:useオプションの引数には, :cl :fooとあるので, パッケージCLとパッケージFOOが提供するすべてのシンボルを自身(パッケージSMAPLE)の中に受け継ぎます.
例えばパッケージCLは, CARというシンボルを提供していますが, パッケージSAMPLEの中では, carとパッケージ修飾子無しにアクセスできます.

この場合, cl:carcl::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は受け継がれません.

shadow-sample.lisp
(defpackage :shadow-sample
  (:use :cl :xxx)
  (:shadow :zzz))
(in-package :shadow-sample)

何のために受け継がないのか

ところで, 何故受け継がないという選択をしたいことがあるのでしょうか. 99個もシンボルを受け継ぐわけですから, 1個くらい受け継ごうが受け継がまいが関係なさそうにも思えます.

単純に, xxx:zzzと明示的にアクセスしたいと言うだけであれば, :shadowオプションを指定せずにその様に書いても良いはずです.
(気づかずに使ってしまうことを防げるくらいのことはあるかもしれませんが.)

実はそれ以上の意味があります. 次のコードを見てください.

shadow-sample.lisp
(defpackage :shadow-sample
  (:use :cl :xxx))
(in-package :shadow-sample)
(defun zzz () 0)

先程の例と同じ様に, パッケージXXXがシンボルZZZをエクスポートする状況だとします.

この時の(defun zzz () 0)は, パッケージXXXZZZを新しくここで定義した関数で再定義するという動作になります.
受け継がれたシンボルに対しては同じ動きをします.

XXXにもSHADOW-SAMPLEにも依存するパッケージがあり, そのパッケージからXXX:ZZZを使うと,
SHADOW-SAMPLEXXX:ZZZが書き換えられたものが使われます.

xxx.lisp
(defpackage :xxx
  (:use :cl)
  (:export :zzz))
(in-package :xxx)
(defun zzz () 42)

元のパッケージXXXで上のコードのようにZZZが定義されていたとしても, パッケージSHADOW-SAMPLEで書き換えられているので, (xxx:zzz)42ではなく0を返します.

これは困りますね. 無論わざとその様にするということもあるのかもしれませんが, あまりその様にして良いパターンは多くないでしょう.

:shadowオプションを使うとこの様なことがなくなります. zzzdefundefvarなどで関数変数定義に使わなくても,
パッケージSHADOW-SAMPLE内にシンボルZZZが存在することになります.

:shadowオプションで複数のシンボルを指定することも出来ます.

defundefvarで使うシンボルZZZを使うとXXX:ZZZのことではなくSHADOW-SAMPLE::ZZZのことを指すことになり, 関数の再定義は起こりません.

衝突を防ぐ

さて, 他のパターンを見てみます. パッケージVVVもシンボルZZZをエクスポートしているとしましょう.

xxx.lisp
(defpackage :shadowing-sample-2
  (:use :cl :xxx :vvv)
  (:shadow :zzz))
(in-package :shadowing-sample-2)

XXXVVVの両方のZZZをパッケージ修飾子無しに使わないで良いのであればこのようにします.

もし, :shadow:zzzを指定しなければ, XXXVVVどちらのZZZか分からない, 別々の二つのものを同じ名前の一つのものとして扱おうとすることになり,
名前が衝突したとしてエラーになります.

:shadowing-import-from

直前の例の場合で, XXXZZZはパッケージ修飾子無しに使いたいとします. この様な場合には:shadowing-import-fromを用います.

xxx.lisp
(defpackage :shadowing-import-from-sample
  (:use :cl :xxx :vvv)
  (:shadowing-import-from :xxx :zzz))
(in-package :shadowing-import-from-sample)

この様に:shadowing-import-fromオプションにパッケージ名続けてシンボル名(複数可能)を書くことで, 同名のシンボルを提供するパッケージから一つを選んで, そこからそのシンボルを受け継ぐことが出来ます.

この様にすることで, パッケージXXXYYYからの(両シンボルZZZによる)の名前の衝突を防ぐとともに, パッケージXXXからはシンボルZZZを受け継いで,
SHADOWING-IMPORT-FROM-SAMPLEパッケージ内で, シンボルXXX:ZZZにパッケージ修飾子なしにアクセスすることが出来ます. 2

まとめ

  • シンボルの名前の衝突を避けるために:shadowオプションを指定できる.
  • 依存している複数のパッケージ間のシンボルの名前の衝突を避けて, 特定のパッケージのシンボルを使う場合には, :shadowing-import-fromオプションを使って指定する.
  • 別のパッケージから受け継いだシンボルについて, 関数定義や変数定義を行うと元のパッケージの関数や変数の再定義となるので注意が必要

  1. この説明は不正確だと思いますが, そもそも変数とは何かみたいなところからの説明をする知識と体力が著者になさそうなので, 詳しい方おられたらコメント等で補足してください. 

  2. SHADOWING-IMPORT-FROM-SAMPLEパッケージ内で, (defun zzz () 1))のようにするとXXX:ZZZが再定義される点は注意です. 

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2