Perl Hackers Hub

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

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

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは普段Perlでスマートフォンゲームのサーバサイドの開発をしているybrliiuこと楠田来安さんで,テーマは「動的なモジュールロードとの付き合い方」です。

本稿に記載しているコードはPerl 5.30.0で動作検証をしています。サンプルコードはWEB+DB PRESS Vol.115のサポートサイトから入手できます。

Perlにおける動的なモジュールロード

言語の柔軟性や文化的な要因により,Perlではほかの言語と比べ,動的なモジュールロードが利用されることが多いです。動的なモジュールロードは柔軟性や実装の手間の削減といった面で多大な恩恵をもたらしますが,それと引き換えに保守性が下がります。

本稿では,Perlにおいて動的なモジュールロードがどう活用されているのか,そして動的なモジュールロードをすると発生する問題とその対策について紹介します。

まずは動的なモジュールロードについての説明と,Perlで動的なモジュールロードをする方法を解説します。

静的なモジュールロード

モジュールロードする場合,通常はuseを使うと思います。useを使った場合,モジュールはコンパイルフェーズ,すなわちコードをコンパイルしてPerl VMで実行できるバイトコードにする際注1にロードされます。本稿ではuseなどコンパイルフェーズで行うモジュールロードのことを静的なモジュールロードと呼びます。

静的にモジュールロードする場合は,プログラム実行前にどのモジュールがロードされるかが決まっている必要があるため,動的にロードする場合と違ってロードするタイミングやモジュールに制約がかかると言えるでしょう。

注1)
Perlインタプリタはソースコードをopcodeと呼ばれるバイトコードから構成される構文木に変換します。それをVMが木構造をたどって命令を実行することでPerlプログラムが実行されます。

動的なモジュールロード

対して動的なモジュールロードは,プログラムの実行時に行うモジュールロードのことを指します。

動的にモジュールロードする場合はロードするタイミングやモジュールを自由に決めることができるため,ロードに時間がかかるモジュールを遅延ロードしたり,ロードするモジュールの名前を動的に生成できます。

動的なモジュールロードをする方法

動的なモジュールロードをする方法のうち,特によく利用される方法を紹介します。

require─⁠─モジュールロードをする組込み関数

Perl組込みの関数を利用して動的にモジュールロードする場合,require関数を利用します。しかし,require関数にBareword注2以外の値を渡した場合,モジュール名を指定したつもりでもファイル名として扱われます。

たとえば次のコードでは,require@INCからFoo::Barというファイルを探してロードを試みます。

my $module_name = 'Foo::Bar';
require $module_name;

require関数にモジュール名としてBareword以外の値を渡したい場合は,require関数を呼び出している箇所全体をevalで評価する必要があります。

my $module_name = 'Foo::Bar';
eval "require $module_name";

しかし,次の理由によりevalrequireを評価するコードはできるだけ書きたくありません。

  • オーバーヘッドが発生する
  • ロード時のエラーが握りつぶされる
  • プログラム外部から渡された値をevalで直接評価するとインジェクション攻撃が可能になる
  • 不格好なコードになる

CPANにはevalをしなくても動的にモジュールロードできるモジュールが存在していますので,requireにBareword以外の値を渡したい場合はそれらのモジュールローダを利用することが推奨されます。

Module::Load─⁠─コアモジュールのシンプルなモジュールローダ

Module::LoadはPerl 5.9.4からコアモジュールとなっている,シンプルなモジュールローダです。コアモジュールなのですぐに使えるほか,直感的にモジュールロードができるので,基本的にはこのモジュールローダを利用するとよいでしょう。

use Module::Load qw( load );
load('Foo::Bar');
Class::Load─⁠─クラスモジュール向けのモジュールローダ

Class::Loadは,クラスビルダ注3であるMooseの裏側などで利用されているクラスモジュール向けのモジュールローダです。ロードするモジュールがすでに内部パッケージやファイル名とパッケージ名が対応していないモジュールによって定義されている場合はファイルを読み込まずエラーにならないので,クラスを作る際内部パッケージでも普通のモジュールでも同じように扱いたい場合に便利です。

次のコードもエラーにならず動作します。

package Foo::Bar;
sub do_something { warn 'Do something.' }

package main;
use Class::Load qw( load_class );
load_class('Foo::Bar');
注2)
シジル(変数各の頭に付く記号)が付かないWordのことを指します。具体的にはパッケージ名,引数のない関数,定数などがBarewordとなります。
注3)
クラスを簡単に作るための機能を提供するライブラリのことです。

useする場合と挙動が異なる点

動的にモジュールロードした場合,useで静的にモジュールロードした場合と挙動が異なる点があるので注意が必要です。

importメソッドが呼ばれない

useした場合,モジュールロード完了後にロードしたモジュールのimportメソッドを呼び出しますが,動的にモジュールロードする場合はimportメソッドは呼ばれません。ですので,importメソッドの中で実行されるはずだった処理を行いたい場合は,ロード後明示的にimportメソッドを呼び出す必要があります。

Module::Loadを利用している場合,autoload関数を利用するかload関数に追加の引数を与えると,ロード後にimportメソッドを呼び出します。

use Module::Load qw( load autoload );

load('Foo::Bar');
Foo::Bar->import();

# もしくは
autoload('Foo::Bar');
括弧を省略してサブルーチンを呼び出せない

別モジュールからuseでサブルーチンをインポートした場合は,サブルーチンの括弧を省略して呼び出せます。

しかし,動的にロードしたモジュールからサブルーチンをインポートした場合は,コンパイルフェーズ時にサブルーチンの宣言がないため括弧を省略してサブルーチンを呼び出せません。

use Module::Load qw( load );
load 'Math::Trig', 'tan';
warn tan 0.9; #エラー

同様に,サブルーチンに定義されているサブルーチンプロトタイプも無視されてしまいます。DSLや独自の構文のような書き方ができるサブルーチンを提供するモジュールを利用する場合は注意が必要です。

use Module::Load qw( load );
load('List::Util', 'first');
warn first { $_ == 1 } 0 .. 10; #エラー

この問題は括弧を省略せず呼び出したり,サブルーチンプロトタイプを使っていなくても呼び出せる方法を使うことで解決できます。

use Module::Load qw( load );

load('Math::Trig', 'tan');
warn tan(0.9);

load('List::Util', 'first');
warn first(sub { $_ == 1 }, 0 .. 10);

あるいは事前にインポートするサブルーチンの宣言を行うことで解決できます。

use Module::Load qw( load );

load('Math::Trig', 'tan');
sub tan;
warn tan 0.9;

load('List::Util', 'first');
sub first(&@);
warn first { $_ == 1 } 0 .. 10;
CHECK,INITコードブロックに書かれたコードが実行されない

CHECKブロック,INITブロックは,それぞれPerlプログラムのコンパイルフェーズ終了時とランタイム開始前に実行される,特殊なコードブロックです。これらのコードブロックは,Perlインタプリタのしくみ上動的にモジュールロードした場合は実行されないので注意が必要です。通常,これらのコードブロックで処理を書くことはないかと思いますが,ごくまれにコンパイルフェーズ終了時に処理を書きたくなるケースがあります。その場合はB::Hooks::EndOfScopeを利用するなど,代替手段の利用を検討してください。

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.118

2020年8月24日発売
B5判/176ページ
定価(本体1,480円+税)
ISBN978-4-297-11566-1

  • 特集1
    開発環境の整備,効果的な議論,評価制度
    実践リモートワーク
    オフィスに集まれない課題の解消方法
  • 特集2
    Pythonデータ可視化入門
    COVID-19/家計調査/財政データで実践!
  • 特集3
    ツールで簡単!
    はじめての脆弱性調査
    Rails,nginx,サブドメイン,DB,OpenSSL

著者プロフィール

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

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

GitHub:https://github.com/ybrliiu