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_fooFoo から Bar へエクスポートしているはずなのに..

$ ./main.pl
Undefined subroutine &Bar::fact_foo called at Bar.pm line 8.

原因

上記のコードで,use の流れを追っていくと以下のようになる.

  1. use Foo; (at "main.pl")
    • require Foo;
  2. use Bar; (at "Foo.pm")
    • require Bar;
  3. use Foo; (at "Bar.pm")
    • require Foo; : すでに Foorequire 済み
    • import Foo;

この import FooBar:: 以下に 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 );
....

ちなみに,以上のことはちゃんと簡潔にモジュールのドキュメントに書いてあった. 調べ始める前にも読んだんだが,見つからなかったんだ...