変数の局所化、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点。

  1. {}ブロックで囲まれている
  2. *setupに処理を何も行わないコードリファレンスを代入している
  3. 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"に戻ります。