localが何をするものかイマイチわからん

ということを言われたので、ふむ確かに最初はわからないだろうなと思い、ここに記録する。

まず、ある学生さんの名言にこんなのがある。

お前は1枚のCDを聞き終わったら、キチッとケースにしまってから次のCDを聞くだろう?誰だってそーする。俺もそーする

つまり、変数を一時的に書き換えるということは、処理を抜ける時に元の値に戻さないといけないということなのです。

しかしコレが実は自分でやろうとすると結構面倒くさいのだ。

とりあえず実装してみよう。

use strict;

our $FOO = 'value';

print $FOO."\n";
main();
print $FOO."\n";

sub main {
    # $FOOのバックアップ
    my $tmp_foo = $FOO; 
    
    # $FOOを上書きする
    $FOO = 'overwrite value';
    
    # 何かしらの処理
    anything();
    
    # キチッとケースにしまう
    $FOO = $tmp_foo;
}

sub anything {
    print $FOO."\n";
}
 value
 overwrite value
 value

と、このように一時的に変更できました。良かったね。

でもmainを抜ける前に値を元に戻さないといけないということは、途中でreturnする時に気を配らないといけないということでなので

sub main {
    my $tmp_foo = $FOO; 
    
    $FOO = 'overwrite value';
    
    my $flag = anything_foo();
    if ( $flag ) {
        $FOO = $tmp_foo;
        return;
    }
    
    anything_bar();
    
    $FOO = $tmp_foo;
}

このように、return毎に元に戻してやる必要があるわけだ。もちろん出口を必ず一つにするというのも一つの手段だけどね。

さてさて、コレで完成!・・・と思ったら大間違い。

実はコレだけではまだ不十分なんだ。それは以下の例を見てくれればわかるだろう。

use strict;

our $FOO = 'value';

print $FOO."\n";
eval {
    main();
};
print $FOO."\n";

sub main {
    my $tmp_foo = $FOO; 
    $FOO = 'overwrite value';
    
    anything();
    
    $FOO = $tmp_foo;
}

sub anything {
    die 'DIE YABOO!';
}
 value
 overwrite value

あれまぁ。値がoverwrite valueのままになってしまっています。mainのスコープを抜けたのに$FOOが元の状態になってないのはおかしいですよね?

なのでevalを使って元に戻してあげる必要があります。

sub main {
    my $tmp_foo = $FOO; 
    eval {
        $FOO = 'overwrite value';
        anything();
    };
    if ( $@ ) {
        $FOO = $tmp_foo;
        die $@;
    }
    $FOO = $tmp_foo;
}

もし、例外が発生したならば変更していた値を元の状態に戻してからdieで死に直すというわけです。

これで完成ですね。ただひとつ注意をしなければならない点としてevalを使う場合はreturnがトラップされてしまうので

sub main {
    my $tmp_foo = $FOO; 
    my $return = eval {
        $FOO = 'overwrite value';
        anything();
        return 'return value';
    };
    if ( $@ ) {
        $FOO = $tmp_foo;
        die $@;
    }
    $FOO = $tmp_foo;
    return $return;
}

と、このようにreturnを引き継いで上げる必要があります。

いやー完成しましたねー。ヨカッタヨカッタ。


と、思いきや実は細かい事を言えばまだまだ問題はあります。

  • dieを一度evalで受け取って再度dieしなおしているのでエラーの発生した場所が一つずれる。
  • なので$SIG{__DIE__}は二度実行される。
  • gotoが使えない。

などなど。色んな懸念点考えてたらもうキリがないよね。

ローカル化したいと思ってもローカル化できないので、そのうち僕は…考えることをやめた。なんてことになりそうです。

さぁさぁ!そこでlocalですよ!

sub main {
    local $FOO = 'overwrite value';
    anything();
}

以上。

一時的に書き換えることだけに特化したこの能力は素晴らしいとしか言いようがない。なんという矢のパワー。

ちなみにlocalはパッケージに属するものと、ハッシュ及び配列のキーに対してのみ使えます。my変数そのものには使えません。残念。

まーそのうち成長してくれるかもしれませんよ。local ACT.2なんて。

具体的なlocalの用途に関しては => To Be Continued