Perlのパッケージとモジュールの話

2012.02.06 0:33
 Perlにはサブルーチンとかライブラリーとかいう考え方があるんですが、基本を学んでいる最中の方にとってはおしらくかなりわかりずらいことだと思うのです。これらのユニットという考え方は実に恣意的なもので、そのやり方もいっぱいあって、様々です。また主義主張によっていろんな考え方があって、そこで呼ばれる名前もいろいろだからです。これにオブジェクト指向なんていうのが加わったらこれまた複雑で、殆ど同じような機能の呼び方を別々の呼び方をしているみたいなことになってきます。ですので、最も最初に理解しなくてはいけないは、単に変数の名前空間ということだけです。これらのスコープを理解できれば、その後のことはそれほど問題ではありません。
 で、前回のサプルーチンの話の続きという感じでパッケージの話をします。パッケージは名前空間を分けているというだけであって、特に何かしら珍しいことをしているわけではありません。PHPなんかでは特に使われることがないだろうし、そもそもそんな巨大プロジェクトに参加したことがないというのであれば、これらのパッケージとかはあんまり使うことがないかもしれません。

 で、パッケージは名前空間をわける役目をしていますが、この名前空間の切り分けがすでにモジュールの機能を有しています。通常のライブラリーとモジュールの何が違うのかというと、単に呼び出し方とその登録パスが違うというだけだと思って構いません。機能は全くかわらないのです。機能自体はかわらないけど、何故にモジュールにこだわるのかというと、その検索パスをまとめておくという便利な機能をつかって、パスから読み出すようなことをしなくてよくて、パスが合わなくてエラーになるようなことを防いでくれるのです。使い出すと結構便利でやめられなくなります。

 さて、@INCとは何かというと、これはPerlが最初から予約している配列変数でライブラリーなどを最初から読み込んでおいてくれているという変数です。Perlのスクリプトにはこの@INCは殆ど出てくることはありません。むしろデバックで使ったり、確認で使ったりするだけです。で、この@INCで読み込まれたパス以下にPerlモジュールが存在すると自動的に読み込んでくれるのです。
 私の環境では、
/usr/local/lib/perl5/5.10.1/BSDPAN
/usr/local/lib/perl5/site_perl/5.10.1/mach
/usr/local/lib/perl5/site_perl/5.10.1
/usr/local/lib/perl5/5.10.1/mach
/usr/local/lib/perl5/5.10.1
.
ということになっていました。レンタルサーバーだとここらのパスは直接触ることができないですが、標準で備えられているモジュールがこの中にはすでにたくさんあるというわけです。しかし、注意すべきは、一番最後の.(ドット)です。これはカレントディレクトリです。つまり、その実行しているファイルパスと同じ場所も@INCに入っているということです。この複数のインクルードパスにあるモジュールをどうやって呼び出すのかというと、それがかの有名なuseです。
use <モジュール名(パッケージ名)>;
 よくよく考えるとこれは変な呼び方で、便利なんですが、むしろ誤解を招くような方法論でもあります。configファイルなんかにだらだらとdefineでパスを書いておいてそれを呼び出せばいいんじゃないか...と思われますが、Perlはそういう方法を取らないんですね。というのはプログラムの設計思想そのものにも由来していて、実はPerlはプログラムのプログラムみたいな本体があって、その本体をつかって様々なモジュールをすでに組み込んでいるんです。
 最近だとFrameworkとか流行っていますが、Rubyなんかは最初からフレームワーク付属でRuby on railsというパッケージで配布していてそれはもうプログラム言語という感じではなくて、印象からすると、こんな感じです。
Ruby → Ruby on rails
日本語 → すぐに使えるビジネス用語
わかりますか?赤ちゃんの言葉から方言、専門用語から外来語、世代間によるすべての造語や流行り言葉も含めて日本語が存在するわけですが、もうすぐに使えるものだけに絞って「ビジネス日本語」というパッケージしてしまうと、働きたい人間にとっては非常に便利ですよね?プログラム言語もそんな風になっています。Perlにしてみれば、サーバーサイドでシェルのように使っている人もいれば、WEBに特化された言語だと思っている人もいたり、PerlはC言語に近い抽象言語だと思っている人もいるし、いろいろなんです。それが全部でPerlなのです。Webという特性で見るとそれはすでに特化された言語の一部であって、Perlの全体ではありません。同様にしてPerlは様々なモジュールを組み込むことでそれらに特化したPerl somethingみたいな言語に早変わりするということになっています。もっとよく考えて設計してからモジュールを読み込むと立派なフレームワークにすらなります。そういうことなのです。
 プログラム自体がいわゆる抽象言語であって、フランス語でいうところのその様々なニヴォーというか英語でいうところのレベルといいますか、そういう風に言葉によってその十分条件とか必要条件があるんです。脊椎動物を扱うのと、哺乳類を扱うのと犬を扱うのとマルチーズを扱うのと、飼っている犬のケンちゃんを扱うのとではその言語の抽象レベルが違うというわけです。犬を扱っている方々は、マルチーズのケンちゃんを扱うことができるわけですが、ケンちゃんを飼っている人にとってその他の犬はどうでもいいのです。そんな風にして、Perlもその扱う空間というかドメインというか、そういうのが広域に渡っている場合と、局所的な場合があるんです。Perlそのものは広域的ですが、モジュールはものすごく局所的です。不思議なことにモジュールをモジュールたるものに仕上げるにはPerlに組み込まれているモジュールを使うんです。(何を言っているんだかわからなくなってきましたが、概ね合っていると思います。)

 では、実際にモジュールを作ってみます。

モジュールを作るにはサブルーチンを作ります

 モジュールを作るにはサブルーチンを作ります。これは、非常に大切です。以前プログラマーと名乗る方がサブルーチンを作成せずに数千行に及ぶコードを書いてくれましたが、これではデバックもバージョンアップも開発もしようがありません。サブルーチンはユニットの最小単位として非常に大切なものです。
 サブルーチンを作っていて、プロジェクトが大きくなると、もしかしたらサブルーチンの名前が重複してしまうんじゃないかと懸念される時がやってきます。その時がモジュールの出番なのです。いや、それはちょっと早いかもしれません。まずはパッケージの出番です。
 パッケージによって名前空間を分けてやることで、その名前の重複を極力避けてあげる作業をします。パッケージは名前空間をわけた一つのライブラリーです。requireで呼ぶこともできます。ここまでのサンプルを以下に書きましょう。

[index.cgi]
#!/usr/bin/perl
print "Content-type: text/html\n\n";

require "include_path_search.pm"; # requireで呼び出し

our @inculde_path;
@inculde_path = &include_path_search::inc(@INC);#パッケージ内のサブルーチンを呼び出す

foreach (@inculde_path) {
    print $_."<br />\n";
}

[include_path_search.pm]
package include_path_search; # これがパッケージ名

#BEGIN {
#    use Exporter;
#    @ISA = qw(EXporter);
#    @EXPORT = qw(&inc);
#}

sub inc {
    return @_;
}

1;
このような感じになります。
 呼び出すファイルは、include_path_search.pmです。これは、include_path_search.plでもinclude_path_search.cgiでも殆ど同様に動きます。もちろんApacheサイドの設定にも依存しますが、だいたい同じものだと思ってもらって結構。しかし、モジュールを組み込む際には、xxxx.pmとするのが習わしです。なので、拡張子を.pmにしてモジュールらしくものを作る準備をしておきます。
 しかしこれは、まだ本来的な意味でのモジュールではありません。モジュールはuseで呼び出したいのです。ここではrequireをつかってパスとファイル名で呼び出しています。

本物のモジュールにしてあげる

 モジュールをモジュールにしてあげるには、パッケージ内でエクスポートしてあげます。どこにエクスポートされるのかというと先ほどの@INC内にエクスポートされる(本当はちょっと違う)と覚えておけばいいでしょう。
BEGIN {
    use Exporter;
    @ISA = qw(Exporter);
    @EXPORT = qw(&inc);
}
 パッケージの最初で、エクスポートしてやります。BEGINを知らない方には補足で説明しますが、BEGIN{}とEND{}というパッケージ内の特別なサブルーチンがあります。これはコンスタクタとデストラクタに非常によく似ています。初期設定と最終的な後始末を行うサブルーチンです。これは今度説明します。
 エクスポートには、Exporterというモジュールを使います。モジュールを作るのにモジュールを使うというわけです。パッケージ内からuseで呼び出せます。で、次に@ISAにExporterという文字列を渡すわけですが、これ、何をやっているのかというと、パッケージの継承を行なっているんです。パッケージをモジュールにする場合にはExporterというモジュールが必要なわけですが、このモジュールがここでの機能に継承されるように@ISAに登録するというわけです。ですから、ここでは複数のその他のモジュールも登録が可能です。逆にいうと@ISAを検索すると継承しているその他のパッケージがわかるというわけです。
そんで最後に@EXPORTというエクスポートするサブルーチン(機能)を登録してあげます。
 何故にサブルーチンを登録するのかというと、どのプログラム言語でもありますが、そのパッケージ内といいますかローカル内だけで機能する関数っていうのがあったりするんです。そういうローカルものは特に外に出す必要がないんです。Perlはサブルーチンを比較的自由に入れ子にできるため、その主なるサブルーチンをエクスポートするだけで十分です。またエクスポートしないサブルーチンはローカル内で厳粛に守られた形になるので、その後の厄介ごとを防ぐ役割も果たします。で、BEGIN{}内で行われるすべてが終了しました。
 この状態で、requireをuseにするとモジュールして読み込まれてパスを設定する必要がなくなります。ちなみに呼び出すべきものは名前空間(パッケージ)そのものという形になります。
[index.cgi]
#!/usr/bin/perl
print "Content-type: text/html\n\n";

use include_path_search;#パッケージ名をそのまま呼び出す。

our @inculde_path;
@inculde_path = &include_path_search::inc(@INC);#パッケージ内のサブルーチンを呼び出す

foreach (@inculde_path) {
    print $_."<br />\n";
}

[include_path_search.pm]
package include_path_search; # これがパッケージ名

BEGIN {
    use Exporter;
    @ISA = qw(EXporter);
    @EXPORT = qw(&inc);
}

sub inc {
    return @_;
}

1;
 実際こんな無意味なモジュールを作る人はいないと思いますが。。。

実質的には、読み込んでいる実際のモジュールがどんなもんかってことで、
package include_path_search;

BEGIN {
    use Exporter;
    our @ISA = qw (Exporter);
    @EXPORT = qw(&inc_search);
}

sub inc_search {
    foreach (@_) {
        $dir = $_;
        opendir(DIR,"$dir");
        while(my $v = readdir(DIR)) {
            if ($v =~ /^\.+$/) {next;}        #.と..をスキップ
            $filename = $_."\/$v";        # 既存のファイル名
           
           
            # get something.pm (extension .pm)
            if ($filename =~ /.*\.pm$/) {
                if (-f $filename) {
                   
                    my $file_size = -s $filename;
                    while($file_size =~ s/(.*\d)(\d\d\d)/$1,$2/){};
                    my $re = push(@all_inc, $filename."[$file_size]");
                }
            }
        }
        closedir(DIR);
    }
    return @all_inc;
}
1;
こんな感じのものを作るとちょっと実用的かもしれません。あ、再帰的に検索しないと意味ないか。






プロフィール



  • Name :: 山上オサム ♂(39)
  • Hobby :: 武術
  • Work :: Web Designer