Exporter を使ったモジュールの継承
おまえは何を言っているんだみたいな話だったらすみません。
たとえば,foo()
という関数を export している ModA
というモジュールがあるとします。
package ModA; use strict; use warnings; use base qw( Exporter ); our @EXPORT = qw( foo ); sub foo { warn sprintf '%s::foo called', __PACKAGE__; } 1;
こいつをたとえば
#!/usr/bin/perl use strict; use warnings; use ModA; foo();
のように使うと,
ModA::foo called at ModA.pm line 12.
のように出ます。
ここまでは当たり前。
use ModB
したら ModA::foo()
を呼び出せるようにしたい
んで,use ModB
ってしたら foo()
って呼び出せて,それは実際には ModA::foo()
が呼び出されるようにしたいとします。
なんとなく継承すればいいのかな,と思って*1
package ModB; use strict; use warnings; use base qw( ModA ); 1;
のように use base qw( ModA );
してやればいいんじゃないかなと思って書くと,
#!/usr/bin/perl use strict; use warnings; use ModB; # instead of 'ModA' foo();
こいつが,
Undefined subroutine &main::foo called at test.pl line 8.
のようにうまくいきません。
というのは use base
や @ISA
を使った継承というのは,あくまで OO で Perl を使うとき(たとえば $instance->foo()
とか KLASS->bar()
とか)のためのものであって,今回やりたかったことというのは実は継承じゃないからなんですね。
ModB
の import()
を定義してみる
Exporter
を使うとパッケージ名(ModA::
)を省略して foo()
と呼び出すことができるのは,use
したときに import()
関数が呼び出されて,そこで main::
名前空間*2に関数を登録するからで……みたいな「むつかしい話」は他にゆずるとして。
ともかく,import()
という関数がキーポイントみたいだから,ModB
を use
したときに ModA
の import()
を呼び出したらいいんじゃないか。と思って下記のように書いてみます。
package ModB; use strict; use warnings; use base qw( ModA ); sub import { my $pkg = shift; return ModA->import(@_); } 1;
ところが,これでもうまくいきません。
というのは,ModA->import()
したときに何がおきるかというと,それを実行したところの名前空間(ModB::
)に関数を導入するからなんですね。ほんとは(今回の場合)main::
という名前空間に導入してほしかったんですけど,それは(コールスタック上)「もう一段上のレベル」だったのです。
Exporter::export_to_level()
が使える
で,その「もう一段上のレベル」の「名前空間に関数を導入する」ためには,Exporter
の export_to_level()
といういかにもな名前の関数を利用します。
今回の例だと,
package ModB; use strict; use warnings; use base qw( ModA ); use Exporter; sub import { my $pkg = shift; return Exporter::export_to_level('ModA', 1, @_); # または ModA->export_to_level(1, @_); } 1;
のようにします。ここで 1
というのが引数に出てきてますけど,これが 0
の場合さきほどの例と同じように ModB::
という名前空間がターゲットになります。で,1
にすると,import()
を呼び出したレベル……今回でいうと main::
……に関数を(Exporter
が)export してくれる,というわけです((むろん 2 以上の export level を指定することもできます。import()
関数に直書きするんじゃなくて他のサブ関数を経由する場合とか。))。
このコードで先ほどのスクリプトを実行すると,
ModA::foo called at ModA.pm line 12.
のように無事 ModA::foo()
が呼び出されます。
ちょっと余談ぽくなりますが,さきほどいったように,これは「継承」じゃないので,ModB
の
use base qw( ModA );
という部分は実はいりません。ただ,その代わりに
use ModA;
としておかないとだめですけど。
実は Exporter
を継承していない場合
これでオッケーと思いきや,ModA
の実装によっては
Can't locate object method "export" via package "ModA" at /usr/lib/perl5/5.8.8/Exporter/Heavy.pm line 217. BEGIN failed--compilation aborted at test.pl line 6.
のように怒られる場合があります。
これは,ModA
の @ISA
に Exporter
が含まれない場合です。たとえば,ModA
の実装が
package ModA; use strict; use warnings; use Exporter 'import'; # instead of 'use base qw( Exporter );' our @EXPORT = qw( foo ); sub foo { warn sprintf '%s::foo called', __PACKAGE__; } 1;
のようになっている場合。この場合でも ModA::import()
は Exporter::import()
を利用してくれるので,普通の export としてきちんと働きます。
しかし,さきほどの export_to_level()
という関数は内部で ModA->export()
という関数を利用していますので,それがみつかんないよ,と怒られるわけです。
対策としては,無理やり ModA
の export()
関数を定義してやればよろしい。
package ModB; use strict; use warnings; use ModA; use Exporter; *ModA::export = \&Exporter::export; sub import { my $pkg = shift; return Exporter::export_to_level('ModA', 1, @_); } 1;
これでうまくいくようになります。
厳密には ModA
で export()
という(別内容の)関数が定義されていた場合に備えて,
sub import { my $pkg = shift; use Exporter; local *ModA::export = \&Exporter::export; return Exporter::export_to_level('ModA', 1, @_); }
のようにしたほうがいいかも。
おわりに
まあ実は Exporter を使わなくても,自分で適切な import()
を書けば関数の export はできます。自分で書いてみるなり,Exporter
とか Exporter::Lite
とかのソースを読むなりすると,シンボルテーブルとかそのへんの知識も深まりますし,上で書いた内容ももっとよくわかると思います。