Perl modules have no notation for custom initializing from external proccess on DECLARATION of "use" or "require". Generally, like a constructor method sub named "new" implements it. But it has a possibility repeating similar initializing proccess on "new" sub and declaration. And I'm afraid that will be a waste operation.
use や require などのモジュール利用宣言の初期化には,宣言時に行なわれるものと,コンストラクタ(new など)の初期化を含む手続きの呼び出しによるものとがある。そのモジュールを使う側特有の条件を「パラメータ」などとして与えて初期化したい時は,通常は後者の方法になるが,宣言時に行なわれた初期化と処理が重複する可能性がある。しかもこの方法は,「使いたい側が希望する条件では使えない」状況の時に,なるべく早い段階で感知したいと思っても,そのコンストラクタ的な手続きを呼び出すまで分からず,結果的に遅い場合も考えられる。「利用宣言時」の段階で分かればそれに越したことはないが,Perl では呼び出す側で設定したい値を「利用宣言時に」パラメータとして指定するなどの方法でモジュールに渡す方法は,特に用意されていない。
「利用宣言時」にカスタマイズ的に初期化する「手段」として Perl
に用意されているものはないが,調べたところ代替的に使えそうな方法があったのでメモ。
● 詳細
一般的にモジュールの初期化は,use 宣言などをした時に行なわれるもの(実質的な BEGIN { …… } 処理)と,コンストラクタ的メソッド(通常は new)など「初期化」を含む手続きの呼び出しによりされるものがある。呼び出す側で独自の値を指定して初期化したい場合,たいていは宣言後にコンストラクタ相当の初期化を含む手続きにパラメータを与えて呼び出す方法になるが,場合によっては,モジュールの使用宣言をした時の初期化とコンストラクタ内で,似たような手続きを繰り返す可能性も考えられ,すると宣言時の初期化は半ば無駄な処理になる。
一方で,もしモジュールの利用宣言をする側にとって目的通りの動作ができない状況があれば,なるべく早い段階……つまり,宣言の初期化時にエラーを返すなどで判明して欲しい場合もあると思われる。
use や require といった利用宣言時点で,呼び出す側の都合で設定した値によってモジュールを初期化できれば,宣言時点の早い段階での動作確認ができるうえ,無駄な初期化処理もせずに済みそうな気がするが,Perl では宣言時に呼び出す側でそのモジュールに対し何らかの値を指定する方法は用意されていないうえ,use や require で呼び出し宣言されるモジュール内から直ぐ外側(そのモジュール使用を宣言した別のモジュールやスクリプトなど)の変数の値を確認する手段もない。そのため,呼び出す側の事情でカスタマイズした「初期化」をしたくても,「宣言」時点でそれをするための手段がなく,このままだと前述の「無駄な初期化」になると思われる処理を回避することはできない。
ただ,perl の変数のうち“^_”で始まるものは,必ずグローバルな(main パッケージに属する≒%ENV などに似た)扱いになるのだとか。これを利用すると,呼び出されたモジュール内からもその値が読める。だから,require でモジュール利用を宣言する BEGIN 手続き内にその名前で変数を設定すれば,それは初期化時にも読めるわけで,「初期化手続き」に反映することができる。さらに,local 宣言をしておけば,BEGIN {} 内でのみ有効となり,他の手続きへの影響も避けられる。
なお,この変数名に {} 括弧は必須らしい(${^_varname} など)。また“^_”自体は予約されているとかで,必ず文字の続く変数にする。
Perl variables named prefix "^_" (e.g. ${^_varname} ) are accessible from any modules. Then in BEGIN { ... } proccess, you set "^_" prefix variable and use a module by "require", the module can access the variable on the declaration, and initialize with the value of the variable, before call initialize sub (e.g. new() constructor).
◆ 例(example)
たとえば,こんなモジュールがあったとする。
package INITABL; my $test; if( exists ${^_INITABLini}->{ test } ){ $test = ${^_INITABLini}->{ test }; } sub testPrint { print __PACKAGE__ .": The variable \$test is \"$test\".\n"; } print __PACKAGE__ .": \$test initialize to the value \"$test\".\n"; 1;
本来 $test はレキシカルな変数のため外部からアクセスできない。呼び出す側から値を操作することはできないが,モジュール内で初期化時に値を ${^_INITABLini} 変数から取り込んで設定している。
呼び出す側は,INITABL の require 宣言前に変数 ${^_INITABLini} にハッシュの参照を置き,その {test} に設定したい値を入れておく。
BEGIN { local ${^_INITABLini}={ test=>"Ok!" }; require INITABL; } INITABL::test();
INITABL: $test initialize to the value "Ok!". INITABL: The variable $test is "Ok!".
実行結果の1行めは BEGIN { …… } 中の require 宣言で呼び出された時に印字されたもので,この時点で既にレキシカル変数 $test に呼び出した側で指定した値が設定できていることを意味する。2行めは呼び出す側5行めの INITABL::test() によって印字されたもの。
この“^_”で始まる変数を使う方法なら,require 宣言時にカスタマイズした初期化をすることが可能と思われる。
● “^_”を使わなくても読めるケース(2021-12-21 追記)
その後いろいろ調べたところ,“^_”タイプの変数を使わなくても,モジュール内で変数に設定した値が(MODULE:: を付けずに)呼び出した側で読める場合があることが判明。ただし以下の条件を満たす場合。
- 呼び出される側で package 宣言をしていない(する前)。実質的に do ファイルと同等な扱いになると思われる。
- とりあえず use でも require でも Ok のよう。ただし,use やBEGIN 内での require 宣言使用は要注意。次節参照。
◆ 初期化したいなら BEGIN 内
あるモジュールを use や BEGIN { require …… } などの優先処理宣言で呼び出した場合,宣言よりも前に記載されている処理でも後回しになる。そのため,ある変数に適切な値を調べて設定するモジュールがあったとして,それより前に「デフォルト値を設定しておく」つもりで代入処理を置いても,優先処理宣言されたモジュール内の値の設定が先で,use や BEGIN 宣言の前に記述された「デフォルト値」の設定処理は後になる。結果的に前に記述された「デフォルト値」設定のまま変化しないように見える。呼び出す側で「デフォルト値」を指定したい時は「BEGIN の中で代入処理する」のが適切と思われる。
一方で,BEGIN { …… } 内に入れずに require 宣言で呼び出すと,宣言より前に使われていた変数もモジュール内でアクセスできる。
なお use では,こうした「宣言直前の代入処理」などはできない。
◆ use 宣言は BEGIN 内に置かないほうがいい
どうやら BEGIN 処理の中に use があった場合も,やはりそちらを先に処理するよう。たとえば BEGIN 処理中,$HOME をホームディレクトリ("/home/username")に設定した直後に use lib "$HOME/lib/perl" という処理を置いても,$HOME に値が設定される前に use が処理されるため,@INC には "/home/username/lib/perl" ではなく "/lib/perl" が追加される。BEGIN の外に出すことで記述順通りの処理になるよう。または,BEGIN 内では use lib ではなく unshift @INC, ... を使う。