Exporter とモジュールの相互呼び出し
モジュールが相互にロードしあっている場合,Exporter
が思い通りに動かない場合がある.
嵌まってしまって,友人にヘルプしてもらいつつ調べたことを,メモとして残す.
事象
例えば,次のようなコードがあったとする.
$ cat main.pl # !/usr/bin/perl use Foo; print fact_foo( 5 );
$ cat Foo.pm package Foo; use base qw( Exporter ); our @EXPORT = qw( fact_foo ); use Bar; sub fact_foo { my $x = shift; return ( $x == 1 ) ? 1 : return $x * fact_bar( $x - 1 ); } 1;
$ cat Bar.pm package Bar; use base qw( Exporter ); our @EXPORT = qw( fact_bar ); use Foo; sub fact_bar { return fact_foo( $_[0] ); } 1;
これを実行しようとすると,&Bar::fact_foo
が未定義であるとエラーが出る.
fact_foo
は Foo
から Bar
へエクスポートしているはずなのに..
$ ./main.pl
Undefined subroutine &Bar::fact_foo called at Bar.pm line 8.
原因
上記のコードで,use
の流れを追っていくと以下のようになる.
use Foo;
(at "main.pl
")require Foo;
use Bar;
(at "Foo.pm
")require Bar;
use Foo;
(at "Bar.pm
")require Foo;
: すでにFoo
はrequire
済みimport Foo;
この import Foo
で Bar::
以下に fact_foo
がインポートされないことが,エラーの直接的な原因である.
では import
は何をやっているのかと言うと,@{引数のパッケージ::EXPORT}
が存在する場合,
そこに含まれているシンボルをコーラーの名前空間に展開する.
そこで import Foo;
実行時の @Foo::EXPORT
の値をデバッガで調べると,"empty array" となる.
よって,import Foo
の時点で Foo
の評価が完了しておらず,@EXPORT
の設定も終わっていない
ことが原因のようだ.
対策
コンパイルタイムに @EXPORT
を設定するように,BEGIN
ブロック内に書けばよい.
package Foo; BEGIN { our @EXPORT = qw( fact_foo ); } use base qw( Exporter ); ....
ちなみに,以上のことはちゃんと簡潔にモジュールのドキュメントに書いてあった. 調べ始める前にも読んだんだが,見つからなかったんだ...