変数の局所化、local宣言の挙動を掴む
local宣言。
変数を局所化する宣言、と説明されているが、どうもピンとこなかった。。そこで、ちょっとlocalについてまとめてみました。
Catalystのsetupメソッド内でもlocalが使われています。local宣言がなぜ必要だったのか。また、そのときの内部の挙動はどうなるのか。
これを理解することで、localの持つ意味も掴んでしまいましょう!
※って間違ってたらごめんさい。
それでは、注目すべきCatalyst.pmのsetupメソッドを見てみましょう。
メソッドの構造は以下のようになっています。
setup { my ( $class, @arguments ) = @_; $class->log->warn("Running setup twice is not a good idea.") if ( $class->setup_finished ); .... $class->setup_plugins( delete $flags->{plugins} ); .... { no warnings qw/redefine/; local *setup = sub { }; $class->setup; } .... $class->setup_finished(1); }
setupメソッドの中で、*setupをごにょごにょして、$class->setupメソッドを呼び出しています。
$class(MyApp)はCatalystパッケージを継承しているので、つまりは....
そう、、なんか無限ループに陥ってしまいそうですよね。
setupがsetupを呼び出して、またそのsetupで....
結論をいうと、setupをlocal宣言することによってそのループを止めているのです。
なぜ止まるのか。この挙動を詳細に追っていきましょう。
localによって何が変わるのか
まずはメソッド宣言から順次追っていきましょう。
sub setup の宣言によって、パッケージCatalystのシンボルテーブルには、"setup"というシンボルテーブルエントリが作成されます。
{main}{Catalyst}{setup} => undef
このsetupシンボルテーブルエントリには、setupという名前を持つ型グロブ*setupが紐付きます。型グロブsetupは、以下のような構造をもっています。
*setup { SCALAR => undef, ARRAY => undef, HASH => undef, CODE => { sub setup{}に記述された処理 }, FileHandle => undef, Format => undef }
従って、{main}{Catalyst}{setup} = *setup という状態が確立します。
一方、何気なくかかれていた↓の処理。
$class->setup_plugins( delete $flags->{plugins} )
ここでは、読み込むべきプラグインを$class(MyApp)の継承チェーン(@ISA)に含めることを行っています。
(今回、詳細は割愛)
この処理によって、
$class(MyApp)の継承チェーンは、
@ISA = ( 'Plugin1', 'Plugin2', .... , ''Catalyst', 'Catalyst::Base'')
と表現される状態になります。
また、それぞれのPluginにsetupメソッドがある場合、
sub setup { .... $c->NEXT::setup(@_); }
として、NEXTによって選択される次のパッケージに処理を移行させる記述がされているはずです。
これで前置きは終わりました。いよいよ、本題の部分にはいっていきましょう。
注目しているのはこの部分です。
{ no warnings qw/redefine/; local *setup = sub { }; $class->setup; }
ここで重要なのは以下の3点。
- {}ブロックで囲まれている
- *setupに処理を何も行わないコードリファレンスを代入している
- no warnings qw/redefine/として、redefineプラグマが無効化されている
まず、{}ブロックで囲むことで、$class->setupを実行する空間を、Catalystパッケージ内に新しく作成します。
このブロックが終了すれば、処理は元のCatalystパッケージに戻ります。
local *setup = sub {};
上の式は、下の式と同値です。
local *setup; -----(1)
*setup = sub {}; -----(2)
localは、変数を局所化する宣言文です。(1)の内部処理は、以下の手順で説明されます。
[避難すべき変数値を保持しておくもの] = {main}{Catalyst}{setup} (現在のsetupシンボルテーブルに含まれる値を格納しておく) {main}{Catalyst}{setup} = undef
※このときに、redefineプラグマがonになっていると、
すでに存在しているパッケージ変数に対して初期化が行われたということでエラーが発生します。
この宣言によって、このブロック内から、Catalyst::setupを参照しても、何も存在しない状態になります。
したがって、(2)の処理によって、新たな関数リファレンス(何も処理を行わない)が代入されることによって、
$class->setup が呼び出されることで、@ISA内をNEXTによってsetupメソッド実行の連鎖が発生するのですが、
Catalyst->setupまで来たところで、処理が終了するようになります。
この状態はロックを抜ける元の状態に戻ります。
他の例
test.txt
arg1 : 1 arg2 : 2 arg3 : 3
open my $IN, 'test.txt' my $count = 1; { local $/; while(<$IN>){ print $count++ . "\n"; } }
結果
1
わかりますよね。$/はある種グローバルな変数ですが、
local $/; ( $/にundefを代入)によって、行区切り文字がundefとなり、
ファイルを一気読みすることができるのですが、
ブロックを抜ければ、その性質は元の状態 $\ = "\n"に戻ります。