この記事は、Perl Advent Calendar 2015の22日目の記事です。
なお、21日目はakihiro_0228によるプロジェクトのユニットテスト用雛形をサクッと作るでした。
最近、use MyModuleの後ろに関数名じゃないものを指定できるモジュールをいくつか使ったり、自分でも作ったりしたので、その時に調べたことをまとめました。
ただし、あんまり使いすぎると黒魔術になっちゃうので、ほどほどが良いようです。
モジュールについて
Perlではモジュールという仕組みが用意されていて、他人が作った機能を自由に自分のプログラムに取り込むことができるようになっています。例えば、Encodeモジュールが提供するencode関数を使いたいときは以下のようにします。
use utf8;
require Encode;
my $text = 'ぱーるあどべんとかれんだー';
print Encode::encode('UTF-8', $text);
いちいちモジュール名を指定するのは面倒ですね。requireではなく、useを使うと関数名だけで実行できるようになります。
use utf8;
use Encode;
my $text = 'ぱーるあどべんとかれんだー';
print encode('UTF-8', $text);
ちょっと短くなりました。
あたかも自分で定義した関数と同じように、Encodeというパッケージ名を指定しなくても別のモジュールの関数が使えるようになりました。これはExporterというモジュールで実現されています。
Exporterモジュール
上記のuseすると関数名だけで呼び出せる仕組みは、Encodeモジュールでは、以下のようにExporterモジュールを使って実現されています。
package Encode;
...
use Exporter 5.57 'import';
...
our @EXPORT = qw(
decode decode_utf8 encode encode_utf8 str2bytes bytes2str
encodings find_encoding clone_encoding
);
Exporterモジュールを使うと、@EXPORTというパッケージ変数に関数名を与えておくことで、その関数名がuseされるタイミングで利用者側のパッケージ名に取り込まれ、関数名だけで呼び出せるようにしてくれます。
いくつか使い方が有りますが、通常は以下のように継承して使うことが多いようです。
package MyModule;
use parent qw/Exporter/;
our @EXPORT = qw/func1 func2/;
sub func1 {
}
sub func2 {
}
1;
Exporterモジュールの実装
Exporterモジュールのコードは以下のようになっています。
sub import {
my $pkg = shift;
my $callpkg = caller($ExportLevel);
...
# shortcut for the common case of no type character
*{"$callpkg\::$_"} = \&{"$pkg\::$_"} foreach @_;
}
パッケージの解説をゼロから始めると長くなるので割愛しますが、perlはuse MyModule qw/func1/;を実行するタイミングで暗黙的にimportという名前の関数を、qw/.../で指定された内容をパラメータにセットして呼び出すようになっています。
Exporterモジュールを継承しているとimportメソッドが定義されているので、このimportメソッドが呼び出され、関数がエクスポートされるわけです。
独自のimport関数
このようにExporterモジュールを使っても良いですが、自前でimport関数を定義する方法も有ります。
useのタイミングでモジュールの挙動をパラメータによって変えたい時などに使います。
例えばTest::Requiresでは、以下のようにuseで指定するのはエクスポートしたい関数ではなく、テストに必要なモジュール名を指定するようになっています。
use Test::Requires qw/Test::Module/;
これはTest::Requiresのimport関数を以下のように定義することで実現されています。
sub import {
my $class = shift;
my $caller = caller(0);
# export methods
{
no strict 'refs';
*{"$caller\::test_requires"} = \&test_requires;
}
# test arguments
...
for my $mod (@_) {
test_requires($mod, undef, $caller);
}
...
}
最初にtest_requiresという関数をエクスポートして、その後にパラメータで与えられているモジュールを引数にtest_reuqires関数を呼び出しているのが分かるかと思います。
おわりに
通常はuse MyModuleの後ろはエクスポートしたい関数名を指定しますが、上記のように独自のimport関数を定義してしまえば、何でもできてしまいます。本当にPerlは油断ならない言語ですね!
なお、次はpawaさんによる「青空文庫を支えるPerl言語」です!!