[技術継承の現場] use = require + import
前回はPerlにおけるuseとrequireの違い、特に記法、評価タイミング、ついでにオーバーヘッドについてつらつらと書きました。
今回はその続き、importについて説明しましょう。
評価タイミングよりもなによりも、こっちの特徴のほうが重要かもしれません。
モジュールをロードするrequireの機能に加え、use にはそのモジュールの機能の一部を import することができます。
importされちゃう例
たとえば
use Data::Dumper;
と書いておくとDumper関数が使えるようになりますね。use File::Pathでmkpathやrmtreeが使えるようになります。
この「使えるようになる」というのは、つまりはuseした場所の名前空間(デフォルトではmainパッケージ)にこれらの関数が勝手に挿入される、ということなのです。これをやるのがimport。useすると Data::Dumperの中にあるimport関数が呼ばれ、そいつが自分ちのDumperをあんたのところから直接呼び出せるようにしてくれる、と。
このimportがもしなかったら。require Data::Dumper であったなら、
Data::Dumper::Dumper(…);
といちいちパッケージをちゃんと全部指定して呼び出さないといけない。あるいはオブジェクト指向的な関数のほうを呼び出さないといけない。
my $dd = Data::Dumper->new(…); $dd->Dump(….);
この「関数のご提供」の仕方はモジュールによってさまざまです。たとえばもうuseした瞬間に勝手に関数が使えるようになっちゃうやり方もあるし、使いたいやつだけuse文の後ろにごにょごにょ書くというタイプもあります。
たとえば File::Copy はファイルをコピーする関数を提供するものですが、useしただけでcopyとかmoveという関数が使えるようになります。しかし以下のようにuseの後ろにインポートする関数を指定して、UNIX流の「cp」「mv」を使うこともできます。
use File::Copy “cp”, “mv”;
普通これダブルクォートが面倒なので qw を使って
use File::Copy qw/cp mv/;
なんて書いたりしますね。
さらに「このグループの関数群だけインポートする」とかができます。
たとえばCGIモジュールなどで
use CGI qw(:netscape);
なんて書くとNetscape独自拡張タグ用関数が使えるようになる、とか。まあいまどきこれ使うひとはなかなかいないと思いますが。
import関数の中身
…とまあ、こうした効能があるのですが、このimport、実際のPerlとしての動作はただ「モジュールのロードが終わったとき、その中にimportという名前の関数があれば実行する」というシンプルな代物です。
さきほどから書いている「関数を呼び出し元のパッケージでも使えるように…云々」というのはそのimportの中でモジュール開発者がそう実装するだけの話です。簡略化して書いてみると以下のようなコードになります。
package Hoge; sub import { my ($pkg, @imports) = @_; my $callpkg = caller(0); for my $sym (@imports) { *{$callpkg."::$sym"} = \&{$pkg."::$sym"}; } } sub prod { my ($a) = @_; return ($a > 1) ? $a * prod($a-1) : 1; } 1;
階乗を計算するprod関数を提供しよう、というものです。*{$callpkg."::$sym"} = …あたりが、元の空間に突っ込んでいる部分ですね。まあちょっと難しいことをしています。useの後ろに指定したシンボルは引数(上記で@imports)として渡されます。
そしてこれを、以下のようにuseすれば、呼び出し元でもprod関数が使えるようになります。
use Hoge qw(prod); print prod(5), "\n";
ただ毎回こんな風に実装する必要はなく、通常はExporterというモジュールを継承すればいいだけです。上記モジュールのようなことをしたければ、
package Hoge2; use base qw(Exporter); our @EXPORT_OK = qw(prod); sub prod { my ($a) = @_; return ($a > 1) ? $a * prod($a-1) : 1; } 1;
use baseと@EXPORT_OKのわずか2行でできます。そのほか、上の例では省略したエラーチェックなどもやってくれます。
ただ、importの仕組みを知っておくのは無駄ではないでしょう。要するにimportは普通の関数ですので、なんでも書ける。「関数などを提供する」という本来の意図とはちょっと違う挙動をこっそりやってくれるモジュール、なんてのが書けるわけですね。たとえば、useするだけで他のモジュールや既存の組み込み関数をこっそり置き換えてくれちゃう、とか…。
といったわけで、「なんだかよくわからないからrequireでいいや」などといわず、use, requireの勘どころを押さえて上手に使い分けていただければと思います。