LoginSignup
14
3

More than 5 years have passed since last update.

Perlにおける意外と便利な local

Last updated at Posted at 2018-12-09

この記事は、 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 って一体何なのでしょうか?

レキシカルスコープとダイナミックスコープ

mylocal の大きな違いは、それがレキシカルスコープなのか、ダイナミックスコープなのかという点です。

まず、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()

14
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
3