Perl Hackers Hub

第60回 動的なモジュールロードとの付き合い方(2)

この記事を読むのに必要な時間:およそ 3 分

前回の(1)こちらから。

動的なモジュールロードで実現できること

動的なモジュールロードをすると実現できることを紹介します。

コード量が少なくなる

ロードするモジュールの名前を動的に生成できるため,useでモジュールロードする場合と比べて書く必要のあるコード量が少なくなります。

たとえば次のように,繰り返し文で複数のモジュールをまとめてロードできます。

use Module::Load qw( load );
for my $module_name (qw/ Foo Bar /) {
    load('MyApp::' . $module_name);
}

動的なディスパッチ

コード量が少なくなることの中でも特筆すべき点として,利用するモジュールを値に応じて動的にディスパッチできることが挙げられます。

項目の多いマスタデータや,ユーザーからの入力など,プログラム外部から渡された値に応じて処理を分岐させたい場合に,コード量が大幅に少なくなるので便利です。

具体的な利用例として,ゲームでアイテムを使用する処理の実装を考えてみます。

データベースにある大量のアイテムデータと,アイテム使用時に実行される効果の処理の対応を愚直に条件分岐で実装すると大変です。しかし,次のようにアイテム名からアイテム効果モジュールのモジュール名を動的に生成し,生成したモジュール名のモジュールを動的にロードするようにすれば,少ないコード量で簡単にアイテムを使用する処理を実装できます。

use Class::Load qw( load_class );
use String::CamelCase qw( camelize );

sub use {
    my ($self, $user) = @_;
    my $module =
        'MyApp::ItemEffect::' . camelize($self->name);
    load_class($module);
    my $effect = $module->new(@_);
    $effect->execute($user);
}

起動/ロード時間の短縮

モジュールロードするタイミングを,モジュールを利用する直前にまで遅らせられるため,アプリケーションの起動やモジュールのロードにかかる時間を短くできます。MooType::Tiny,Class::Accessor::Liteなど,パフォーマンスを意識しているCPANモジュールや,巨大なWebアプリケーションでこの用途による利用をよく見かけます。

依存関係の動的な解決

依存関係を動的に解決できるので,条件に応じて依存するモジュールを変更したり,モジュールロードに失敗した場合の処理を書けます。また,モジュールが相互に依存していてもサブルーチン再定義による警告が発生しないように実装できます。

動的なモジュールロードの活用

これらの動的なモジュールロードで実現できることが,具体的にどう活用されているのかを見ていきましょう。

コード量を少なくする

コード量を少なくするために活用されている事例を見てみましょう。

Module::Findの事例─⁠─特定の名前空間に属するモジュールをすべて読み込む

Module::Findは,ある名前空間の下に属するモジュールをまとめてロードできるライブラリです。usesubで引数の名前空間直下に属するモジュールをロードし,useallで引数の名前空間に属するモジュールを再帰的にロードします。

use Module::Find qw( usesub useall );

# Foo::Bar, Foo::Baz, ...
my @sub_modules = usesub('Foo');

# Foo::Bar, Foo::Bar::Hoge, Foo::Baz, etc...
my @all_modules = useall('Foo');
Catalystのコンテナ─⁠─use忘れを防ぎ,モジュール名を短縮する

CatalystというPerl製のWAFWeb ApplicationFrameworkがあります。Catalystではアプリケーションの起動時にすべてのモデルモジュールをロードし,モデルクラスのインスタンスをキャッシュするコンテナのようなしくみがあり,各モデルの操作はキャッシュされたインスタンスを取得して行います。これにはuse忘れがなくなる,モジュール名が短縮された状態でメソッドを呼び出せる,一度生成したモデルクラスのインスタンスをキャッシュできるというメリットがあります。

実際にCatalystで作られたアプリケーションでどのようにモデルモジュールが使われるのかを見てみましょう。

package MyApp::Controller::Root;
use Moose;
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller' }
__PACKAGE__->config(namespace => '');

sub foo :Local :Args(1) {
    my ($self, $c, $name) = @_;
    my $country =
        $c->model('DB::Countries') ―(1)
            ->search({ name => $name }); ―(2)
    $c->stash(
        template => 'foo.tt',
        country => $country,
    );
}

このコードはCatalystのコントローラのコードになります。

前述したとおり,すべてのモデルモジュールは起動時にロードされるため,ここでモジュールロードをする必要はありません。

(1)では,起動時にロードしキャッシュしたモデルクラスMyApp::Model::DB::Countriesのインスタンスを取得しています。

(2)では(1)で取得したインスタンスからsearchメソッドを呼び出しています。

Catalystのコンテナ機能と似た事例にモデルモジュール専用のモジュールローダがあり,Catalystとは別のWAFのプラグインなどで見受けられます。これは起動時にすべてのモデルモジュールのロードをするのではなく,モデルモジュールのインスタンスを初めて取得しようとするタイミングでモデルモジュールのロードをします。

著者プロフィール

楠田来安(くすだらいあん)

学生時代からPerlを使いCGIゲームの開発運営などを行う。2018年に株式会社モバイルファクトリーに新卒入社し,現在はソーシャルゲームの開発に携わりつつ,Perlコミュニティなどで活動している。

GitHub:https://github.com/ybrliiu