この記事は、 Perl Advent Calendar の9日目の記事です。
Perlにおけるlocal
とは一体何なのか?
まずは Perl Doc を読むところからでしょう。
You really probably want to be using my instead, because local isn't what most people think of as "local". See Private Variables via my() in perlsub for details.
え、 my
を使いたいんだろ?って書いている.....
そうなんです、Perlにおいて、(レキシカルスコープにおける)局所変数を宣言する場合には、 my
を使うべきですし、大凡全ての場合において、 my
を使うことにこだわるべきなのです。
では、 local
って一体何なのでしょうか?
レキシカルスコープとダイナミックスコープ
my
と local
の大きな違いは、それがレキシカルスコープなのか、ダイナミックスコープなのかという点です。
まず、2つの共通点は、宣言したスコープの外からは参照出来ないということです。
そして、2つが異なる点は、その宣言の意味と、有効なスコープです。
まず、 my
は宣言したブロック内でのみ有効な局所変数を宣言します。
一方、 local
は局所変数を宣言するのではなく、グローバル変数(パッケージ変数)に対して、ブロック内で有効な一時的な値を与えるのに使用されます。
そして、my
の有効範囲は、宣言したブロック内(字面のみから静的に決まるのでレキシカルスコープ)なのと比較して、 local
の有効範囲は宣言したブロックから呼び出される全てのコード(実行時に動的に決まるのでダイナミックスコープ)になります。
例えば、以下のコードを実行すると、 local $bar
が宣言されたスコープ内で foo
が実行された時のみ、 local bar
が出力されます。
use strict;
use warnings;
use utf8;
use feature qw/say/;
our $bar = 'our bar';
sub foo {
say $bar; # This references a package variable.
}
{
foo(); # もともとの `our bar` が出力される
}
{
my $bar = 'my bar';
foo(); # `foo()` 内のコードは、レキシカルスコープ外なので "our bar" が出力される
}
{
local $bar = 'local bar';
foo(); # `foo()` 内のコードは、ダイナミックスコープ内なので、 "local bar" が出力される
}
{
foo(); # `local` 宣言されたダイナミックスコープの外なので、もともとの "our bar" が出力される
}
通常、実行時に参照される値を変えたいような需要に関しては、サブルーチンの引数を使う等の分かりやすい手段が存在する為、local
の出番はありません。
では、どんなときに、このダイナミックスコープが便利になるのでしょうか?
便利な部分の考察
まず、今でも local
を使うべき 3箇所については、 perldoc に書かれているので是非参照してください。基本的には 一時的に 値を変更したい場合に使う形になっています。
では、どうして、そのような場合に local
が便利になるのかを考えてみます。
便利その1: スコープを抜けた際に、必ず元の状態に復帰することが保証されている
local
の強力な点は、それが言語レベルでサポートされているということです。
例えば、一時的に $Foo::CONFIG
を書き換える例を見てみましょう。(一時的に書き換えるということは、他の処理に影響を及ぼさないように、戻す必要があります)
{
package Foo;
our $CONFIG = {};
}
# local を使った場合
{
local $Foo::CONFIG{AAA} = 'BBB';
do_something();
}
# local を使わない場合
{
my $prev_aaa = $Foo::CONFIG{AAA};
$Foo::CONFIG{AAA} = 'BBB';
eval { do_something(); };
$Foo::CONFIG{AAA} = my $prev_aaa;
if ($@) {
die $@;
}
}
みて頂けると分かるように、 local
を使った場合の方が、非常に簡潔な記述が可能です。
特に、「スコープを抜けた際に、必ず元に戻る」ということが言語レベルでサポートされているため、例外発生時の処理も含めて簡潔で安全な運用が可能です。特に、 %SIG
等について一時的な値を設定する場合などについては、顕著になると思います。
便利その2: 呼び出し先がどれだけ深くとも、キチンと挙動を変更出来る
local
の便利な点としては、それがダイナミックスコープであるため、そこから呼ばれる如何なるコードに対しても、影響を及ぼすことが可能だという点です。(引数を引き回して状態を伝えていく必要が無い)
例えば、以下のコードを見てみましょう。
これは、一時的に INT
による割り込みを無効にして、 Foo::bar
のサブルーチンを書き換えて、非常に重い処理をするようなコードになっています。
{
no strict qw/refs/;
no warnings qw/redefine/;
local $SIG{INT} = 'IGNORE';
local *Foo::bar = sub { 'local bar' };
some_really_heavy_procedures();
}
ダイナミックスコープを利用することで、 some_really_heavy_procedures
の中身がどのように実装されているかに関わらず、 INT
による割り込みの無効化、および、 Foo::bar
サブルーチンの変更が全てに適用されます。
例えば、 既存コードの some_really_heavy_procedures
の内部の様々な局面で Foo::bar
が呼ばれるような実装になっており、綺麗な設計に変更するコストが非常に高い場合には、トップレベルでの黒魔術が功を奏す場合もあります。($SIG{INT}
は黒魔術とかではなく、必ずlocal
を使うべきものです。そして、黒魔術を使っているということが、誰の目にも明らかになるような設計にはしましょう)
まとめ
ということで、 local の意外に便利な側面を見てきました。
繰り返しになりますが、殆どの場合において、我々に必要なものは my
であって、 local
ではありません。
が、時として、 local
が非常に便利に使える局面もあります。
用法用量を適切に守りさえすれば、非常に便利に使えるので、選択肢の一つに入れることは良いのではないでしょうか?
There's More Than One Way To Do It
参考
もっとキチンと勉強したい方は、是非 perldoc を参照して頂けると助かります。
https://perldoc.perl.org/perlvar.html
https://perldoc.perl.org/functions/local.html
https://perldoc.perl.org/perlsub.html#Temporary-Values-via-local()
https://perldoc.perl.org/perlsub.html#When-to-Still-Use-local()