変数の局所化

~ my と local どう違う? ~

ここでは、変数の局所化に使う mylocal について解説します。 どちらも変数を局所化することには違いないのですが、細かな点で違いがあります。 この違いを理解していないと想定しない結果となってしまいます。 しっかりと押さえておきたいポイントです。

目次

局所化とは

そもそも局所化とは何かを理解しなければいけません(ご理解されている方は飛ばしてください。)。

数十ステップ、数百ステップ程度のスクリプトの場合には、全体を把握しながら作成できるため、あまり気にしませんが、 数千ステップ以上のスクリプトともなると、自分でスクリプトを書いていても、どこにどんな変数名を使ったかを忘れてしまいがちです。 Perl の場合、宣言しなくても、いきなり変数を使うことができるため、そのようになりがちです。 そこで、ある範囲だけに有効な変数として定義することで、他の範囲には影響しないようにすることができます。 具体的に、「ある範囲」とは、中括弧 {} に囲まれた範囲のことです。 中括弧内であれば、サブルーチンに限らず、ifwhile, foreach 等でも同様です。

このように、ある範囲だけで有効になるよう変数を定義することを局所化と言います。 そして、局所化された変数のことをローカル変数と呼びます。 ちなみに、スクリプトのどこでも有効になる変数のことをグローバル変数と呼びます。

ただ単に、Perl スクリプト内で、

$var = 'hello';

とすると、変数 $var はグローバル変数となります。 つまり、スクリプト内のどこでもこの変数の値を参照したり変更することが可能となります。 しかし、$var という変数名をすでに使っていることを忘れて、 別の場所で別の目的で $var を使ってしまうと、期待する結果が得られなくなることは明白ですね。 そこで登場するのがローカル変数なのです。もしサブルーチン内で、

sub hoge {
    my $var = 'hello';
    ...
}

とすると、変数 $var は、そのサブルーチン内だけで有効になり、 ほかの場所からはこの変数を参照することができません。 数千ステップにもなるような大規模なスクリプトの場合、 できる限り再利用ができる部品つまりサブルーチンとして作成していきます。 スクリプト全体が、サブルーチンの寄せ集めといっても過言ではないかと思います。 その場合、サブルーチンはできる限りブラックボックス化すべきで、 その中で使っている変数が他の場所から影響を受けるようではいけないわけですね。 そうしないと、保守性が悪くなり、問題が起こったときにどこをどう直せばよいかを、 作成者本人ですらわからなくなってしまいます。 複数人で作成するようなプロジェクトの場合にはなおさらです。 そのために、サブルーチンが使う変数は他から影響を受けないようにしなければいけないわけです。

私の経験上、ほとんどの変数はローカル変数として定義しても差し支えありません。 グローバル変数として定義するのはスクリプトの設定値などの定数くらいですね。 すでに紹介したように、Perl で変数を局所化するには my または local を使います。 以降はそれらの違いを解説していきます。

my について

my は Per5 から実装され、Perl4 では使えません。 Perl4 では local のみが使えましたが、Perl5 では my を使うのが一般的ですので、 しっかりと理解しましょう。

my を使って定義された変数は レキシカルスコープ変数 と呼びます。 静的スコープ変数と呼ぶこともあります。 そして、my を使って変数を宣言することをレキシカルスコープ宣言と呼びます。

次のように my で変数を宣言することができます。

my $var;           # 宣言のみ
my $var = 'hello'; # 宣言と同時に値も同時に格納
my($foo, $var);    # 複数の変数名を同時に宣言

次にレキシカルスコープ変数の有効範囲について説明します。 my で宣言された変数は、sub による関数に限らず、 if, for などによるブロック({...})で囲まれた範囲で有効です。 このスコープのことをブロックローカルと呼びます。

このように Perl の my はブロックローカルを作りますが、プログラミング言語によっては 関数の範囲だけ有効に関数ローカルというものもあります。 Perl には関数ローカルを作る方法はありません。

実例を見てみましょう。次の例を見てください。

$var = 'サブルーチンの外';
print $var; #「サブルーチンの外」が出力される
&example;
sub example {
    my $var = 'サブルーチンの中';
    print $var; #「サブルーチンの中」が出力される
}
print $var; #「サブルーチンの外」が出力される

この場合、2 行目で出力される値は「サブルーチンの外」です。 そして 6 行目で出力される値は「サブルーチンの中」となります。 サブルーチンの中で、変数 $var のレキシカルスコープ宣言をしているため、 新たにメモリー上に値が保持されることになります。 もちろん、2 行目で定義した $var の値も保持されたままです。 それぞれの $var は、同じ名前にもかかわらず、変数名も値も、別々に保持されます。 つまり、それぞれの $var は、名前は同じですが、まったく別の変数として処理されます。 ただし、5 行目のサブルーチン内で定義された $var の変数名と値は、 サブルーチンを抜けた時点で破棄されてしまいます。

では、8 行目では何か出力されるのでしょうか。 上の説明でお分かりかと思いますが、「サブルーチンの外」が出力されるわけです。 サブルーチンの外からは、5 行目で定義した値は参照できないことに注意してください。

次に、5 行目で my を使って宣言しなかった場合にはどうなるでしょうか。

$var = 'サブルーチンの外';
print $var; #「サブルーチンの外」が出力される
&example;
sub example {
    $var = 'サブルーチンの中';
    print $var; #「サブルーチンの中」が出力される
}
print $var; #「サブルーチンの中」が出力される

上記の通り、8 行目の値が変わってしまいましたね。 5 行目で my を使って局所化していなかったため、 1 行目で代入した値が 5 行目で書き換わってしまったのです。 そのため、8 行目の出力結果も変わってしまったというわけです。 意図的にそうしたいのであれば問題ないのですが、そうでなかったらバグになってしまいますね。

次に、サブルーチンがネスト(入れ子)になったパターンを紹介します。 ここで言っているネストとは、サブルーチンの中でサブルーチンを呼び出した場合のことを指しています。

$var = 'サブルーチンの外';
&example;
sub example {
    my $var = 'サブルーチンの中';
    print $var; #「サブルーチンの中」が出力される
    &example2;
}
sub example2 {
    print $var; #「サブルーチンの外」が出力される
}

この例では、4 行目でレキシカルスコープ変数として $var を宣言していますので、 この $var の有効範囲は 7 行目までとなります。 しかし、その手前の 6 行目でサブルーチン example2 を呼び出していますね。 その場合、4 行目で宣言された $var example2 の中でも有効になるのでしょうか。 答えは Noです。 9 行目で呼び出している $var は、グローバル変数の $var で、1 行目で宣言されたものなんです。

話が若干それますが、もし、変数をサブルーチンに引き継ぎたい場合は、以下の例のように引数をを正しく与えるべきです。

$var = 'サブルーチンの外';
&example;
sub example {
    my $var = 'サブルーチンの中';
    print $var; #「サブルーチンの中」が出力される
    &example2($var);
}
sub example2 {
    my($var) = @_;
    print $var; #「サブルーチンの中」が出力される
}
print $var; #「サブルーチンの外」が出力される

local について

local を使って定義された変数は、ダイナミックスコープ変数と呼びます。 動的スコープ変数と呼ぶこともあります。 そして、local を使って変数を宣言することをダイナミックスコープ宣言と呼びます。

次のように local を使って変数を宣言することができます。

local $var;           # 宣言のみ
local $var = 'hello'; # 宣言と同時に値も同時に格納
local($foo, $var);    # 複数の変数名を同時に宣言

次の 2 つの例をご覧ください。

$var = 'サブルーチンの外';
print $var; #「サブルーチンの外」が出力される
&example;
sub example {
    local $var = 'サブルーチンの中';
    print $var; #「サブルーチンの中」が出力される
}
print $var; #「サブルーチンの外」が出力される
例 1
$var = 'サブルーチンの外';
print $var; #「サブルーチンの外」が出力される
&example;
sub example {
    $var = 'サブルーチンの中';
    print $var; #「サブルーチンの中」が出力される
}
print $var; #「サブルーチンの中」が出力される
例 2

お気づきでしょうが、ここまでは、使い方、結果ともに、my とまったく違いはありません。 しかし、Perl 内部での扱い方はまったく違うものになっています。 local では、変数名は別々に保持されません。 言い換えると、local は、グローバル変数に一時的な値を付けるといえます。 Perl 内部では、それぞれの $var は、同じ変数として認識しており、 コードの場所によって値を付け替えるという作業をしています。 そして、どこからその変数が呼び出されたかによって、適用する値が異なってくるのです。 それがダイナミックスコープと呼ばれる所以です。

上記の通り、local は、グローバル変数に一時的な値を付けるということから、 正確に言うとローカル変数を作成するものではないことに注意してください。 本当のローカル変数を定義したい場合には、my を使うべきなのです。

最後に、サブルーチンのネストについてご紹介します。 my の場合の例とコードの内容は同じなのですが、挙動が異なります。

$var = 'サブルーチンの外';
&example;
sub example {
    local $var = 'サブルーチンの中';
    print $var; #「サブルーチンの中」が出力される
    &example2;
}
sub example2 {
    print $var; #「サブルーチンの中」が出力される
}
print $var; #「サブルーチンの外」が出力される

注意して見ていただきたいのは 9 行目です。 my の場合には、$var は 1 行目で宣言された値「サブルーチンの外」が出力されました。 ところがこの例の場合では、4 行目で宣言された $var の値が引き継がれて表示されます。 4 行目で宣言されたダイナミックスコープ変数 $var は、サブルーチンが終了するまでの 7 行目まで有効になります。 local で宣言された場合、7 行目より前で呼び出されたサブルーチンにも値が引き継がれていくのです。