| Top Page | プログラミング | Perl 目次 | prev | next | 索引 |

17. 変数の局在化で安心して使えるサブルーチンを作る

すべての変数がどこからでも見える危うさ

このページのテーマは変数の局在化です. と言ってもなんのことだかよく分かりませんね. まず,下の例を見てください.サイコロをいくつかまとめてふって, 1が出た数を返すサブルーチンを 100 回呼び出してます. たぶん,ポアソン分布の実験でもしたいのでしょう.

for ($i = 0; $i < 100; ++$i) {      #  100回試す.
    $n_hit = &cast_dice_for_1(10);  #    10個のサイコロを振り1の目の数を返す
    print $i, "\t", $n_hit, "\n";   #  何試行めかと,1の数を表示
}

#  引数で与えた個数のサイコロを振って,1がでた個数を返すサブルーチン

sub cast_dice_for_1
{
    $prob_of_1 = 1/6 ;    # 1の目が出る確率

    $n_try = shift @_;    # 最初の引数は,振るサイコロの個数
    $n = 0;               # 1の出た数の積算用変数.

    for ($i = 0; $i < $n_try; ++$i) {
        ++$n if (rand() < $prob_of_1);  # 1なら積算用変数の値を1増やす
    }
    return $n;    # 1の出た数を返す.
}    

これで,100 行にわたってこんな出力が表示されることを期待しているのですが…

0       2
1       2
2       4
3       0
4       1
....
97      0
98      5
99      2

実際は,こんな出力が無限に続きます.

10      1
10      3
10      3
10      4
10      0
10      0
10      2
....

…しかたがないので Ctrl-C でストップ.はて,なにがいけないんでしょう? $i という変数の値の変化に注目して,プログラムの流れを追ってみてください.

分かったでしょうか.メインのプログラムで100回の繰り返しを数えるために 使ってる変数 $i の値が,サブルーチン中で変更されてしまってます. サブルーチンの中でも,サイコロの個数を数えるところで $i を 使っていて,サブルーチンの実行を終わったところでは $i は必ず 10 に なります(10 まで増えたところで $i < $n_try; が偽になる). メインの側では $i が 100 になったら終了するつもりなのに,これでは いつになっても終われません.これが無限ループに陥ってしまった原因です (いつまでも繰り返されて永遠に終了しないループを無限ループと呼ぶ).

とりあえずは,サブルーチン中の $i を $j などほかの名前にすれば ちゃんと動きそうです.でも,どこかほかで $j という変数を使って いると,また誤動作する可能性があります. 誤動作といっても,プログラム自体は書かれたとおりに動いてるのですが, そのプログラムが人間の意図を正しく表現していないので期待した通りには 動いてくれないという, 論理の間違いを含んだプログラムということになります. 「プログラムは思ったとおりには動かない,書いた通りに動く」. 上のようなサブルーチンは,そんなプログラムのもとです.

サブルーチンを書くたびに, この変数名はよそで使ってないか心配しなきゃいけない,呼び出す側でも サブルーチンのなかでどんな変数名が使われてるか心配しなきゃいけない, というのでは不便です.


my をつかって'ここだけの話'をする

この危険を避ける根本的な方法は,my という関数を使うことです. my のうしろに変数名を指定する(my 宣言する)と, その変数は,my 宣言を取り囲む一番内側のブロック ( {} で囲まれた部分), あるいはサブルーチンの中でだけ有効なものなものになります. その外で同じ名前の変数があったとしても,それとはまったく無関係です.

さっそく例を見てみます.全然実用的でない,my の効果を見るためだけの例です.

$x = 10;                                #  グローバルな $x
print "MAIN: x = $x; y = $y; z = $z\n"; #  サブルーチンを呼ぶ前の $x, $y, $z
&show_x_y_z();                          #  サブルーチンを呼んでみる.
print "MAIN: x = $x; y = $y; z = $z\n"; #  サブルーチンを呼んだあとの $x, $y, $z

sub show_x_y_z
{
    my $x = 30;    # $x はこのサブルーチン内でのみ通用
    my $y = 50;    # $y はこのサブルーチン内でのみ通用
    $z = 70;       # $z はグローバル
    print "SUB : x = $x; y = $y; z = $z\n"; #  サブルーチン中の $x と $y
}

最初の行のコメントに,グローバルな $x と書いてあります.グローバルとは, プログラム中の全域から見えてる,参照できる,値を代入できるということです. そのような変数はグローバル変数と呼びます. グローバルでない変数は一般には局所変数とかローカル変数などと呼びますが, Perl ではレキシカル変数と呼ぶようです.

上のプログラムを実行すると,

MAIN: x = 10; y = ; z = 
SUB : x = 30; y = 50; z = 70
MAIN: x = 10; y = ; z = 70

のように表示されます.$x, $y, $z それぞれ注意して見てくさい.

サブルーチン内の変数名はみな my 宣言することにすれば, ほかでどんな変数名を使ってるか気にせずにサブルーチンを書けます. また,呼び出す側としては,サブルーチン内の変数がすべて my 宣言されている ことだけ保証されているならば,その内部でどんな変数名が使われているか まったく知る必要がありません.

では,無限ループに陥っていた最初のプログラムに手を入れていましょう.

for ($i = 0; $i < 100; ++$i) {      #  100回試す.
    $n_hit = &cast_dice_for_1(10);  #    10個のサイコロを振り1の目の数を返す
    print $i, "\t", $n_hit, "\n";   #  何試行めかと,1の数を表示
}

#  引数で与えた個数のサイコロを振って,1がでた個数を返すサブルーチン

sub cast_dice_for_1
{
    my $prob_of_1 = 1/6 ;    # 1の目が出る確率

    my $n_try = shift @_;    # 最初の引数は,振るサイコロの個数
    my $n = 0;               # 1の出た数の積算用変数.

    for (my $i = 0; $i < $n_try; ++$i) {
        ++$n if (rand() < $prob_of_1);  # 1なら積算用変数の値を1増やす
    }
    return $n;    # 1の出た数を返す.
}    

サブルーチン内の変数には,みな my 宣言されています. for の () のなかにも my を書くことができます.

ちょっと注意.サブルーチンが返している $n という変数は外からは見えないはず, それを返して大丈夫なのか?と疑問に思うかもしれません.大丈夫です. サブルーチンが返しているのは変数のであって,変数 (メモリの,ある場所)そのものではないからです. 安心して局所的な変数の値を返しましょう.

サブルーチン内で作る変数は,とくに外部との情報のやりとりに必要な場合 以外は原則として my 宣言する習慣をつけとよいでしょう.

また,my はサブルーチンの中に限らず任意の場所に書けます. for,foreach,while などの繰り返し文のブロック中などに書くと, ブロック内と外部との無用な干渉を避けられます.



| Top Page | プログラミング | Perl 目次 | prev | next |