2016-05-03 14 views
13

Perl 5.20では、forループはモジュールスコープの変数を変更できますが、親スコープの字句変数は変更できないようです。forループは `my`変数を変更しませんが、` our`変数を変更します

#!/usr/bin/env perl 
use strict; 
use warnings; 

our $x; 

sub print_func { 
    print "$x\n"; 
} 

for $x (1 .. 10) { 
    print_func; 
} 

印刷物あなたが期待するように1〜10は、しかし、次のようにはありません:

Use of uninitialized value $x in concatenation (.) or string at perl-scoping.pl line 8. 

はここで何が起こっている:

#!/usr/bin/env perl 
use strict; 
use warnings; 

my $x; 

sub print_func { 
    print "$x\n"; 
} 

for $x (1 .. 10) { 
    print_func; 
} 

は、次の警告を10回照射しますか? perlサブルーチンはネストすることはできません(常にモジュールスコープを持つことができます)。したがって、変数myを閉じることができないことは論理的です。その場合には、strictモードでperlは次のようなメッセージとともに、第2のプログラムを拒否すべきであるように思える:

Global symbol "$x" requires explicit package name at perl-scoping.pl line 6. 
Global symbol "$x" requires explicit package name at perl-scoping.pl line 9. 

すなわち、変数が宣言されていないため、フリー変数はどこでも宣言されていないため、サブルーチンを拒否する必要があります。

なぜPerlはこのように動作していますか?

答えて

17

ループ反復子変数をレキシカルではなく暗黙的にローカライズされたグローバルにする悪い決定に起因すると思われる、混乱しますが、文書化された動作です。 〜からForeach Loops in perlsyn

変数の前にキーワードmyがある場合、その語は字句的にスコープされているため、ループ内でのみ表示されます。それ以外の場合、変数はループに暗黙的にローカルであり、ループを終了すると元の値に戻ります。変数が以前にmyで宣言されていた場合、変数はグローバル変数ではなくその変数を使用しますが、それでもループにローカライズされています。

別の言い方をするには、ループイテレータは常にループにローカライズされています。グローバルであれば、ループブロック内にlocalと宣言されているように動作します。それが字句の場合は、ループブロック内にmyと宣言されているように動作します。

これを2つの例に適用すると、何が起こっているのかを理解するのに役立ちます。

our $x; 

sub print_func { 
    print "$x\n"; 
} 

for $x (1 .. 10) { 
    print_func; 
} 

このループには暗黙的なlocal $xがあります。 localは実際にはtempという名前にしてください。グローバル変数の値を一時的にオーバーライドします。ですが、それでもグローバル変数はです。だから、print_funcがそれを見ることができます。

スコープの終了時に古い値が復元されます。 forループの後にprint $xを追加すると、これが表示されます。

use v5.10; 

our $x = 42; 

for $x (1 .. 10) { 
    say $x; 
} 

say $x; # 42 

のはレキシカルを含むあなたのコード(my変数)を見てみましょう。本当にここ何が起こっている

my $x; 

sub print_func { 
    print "$x\n"; 
} 

for $x (1 .. 10) { 
    print_func; 
} 

は、2つのレキシカル変数$xと呼ば両方を持っています。 1つはファイルスコープ、もう1つはループスコープです。 forループの内側の$xは、外側の$xよりも先行しています。これは「シャドーイング」として知られています。

レキシカルは物理的な範囲外には見えません。 print_func()は、外側が初期化されていないと見なします$x


これからいくつかの文章を書いてみましょう。

常にパラメータを関数に渡します。

実際には、print_funcには引数が必要です。次に、複雑なスコープ規則について心配する必要はありません。

sub print_func { 
    my $arg = shift; 
    print "$arg\n"; 
} 

for $x (1..10) { 
    print_func($x); 
} 

常にfor my $xを使用しています。

複雑な暗黙のforループスコープ規則に依存しないでください。 myでループイテレータを宣言してください。

for my $x (1..10) { 
    print_func($x); 
} 

グローバルを避けてください。

グローバルにアクセスするものを知るのは難しいので、使用しないでください。グローバルが必要だと思ったら、ファイルスコープのレキシカルへのアクセスを制御する代わりに、関数を記述します。

my $Thing = 42; 
sub get_thing { return $Thing } 
sub set_thing { $Thing = shift; return } 

彼らが使用している場所の近くに、あなたの変数を宣言します。

古いコーディングスタイルは、すべての変数をファイルまたは関数の先頭に宣言するようなことを行います。これは、変数が特定の場所でのみ宣言されることを要求する、非常に非常に古い言語からの保留です。 Perl、そして最も現代的な言語には、このような制限はありません。

変数を一度に宣言すると、その変数が何であるかを知ることが難しく、何が使用されているのか、それに影響を与えるのかを知ることは困難です。あなたがそれに影響を与えることを制限する最初の使用に近いと宣言し、それが何のためにより明白になるか。

+0

'$ x(...){...}'のように聞こえるのは 'do {local $ x; $ x(...){...}}; 'に対して代入を行うと' 'Perl-scoping.plで字句変数$ xをローカライズできません...なぜそうではないのですか?元のバージョンでもエラーが発生しましたか? –

+2

@GregoryNisbet 'local'はレキシカルでは動作しないので、' my $ x'がすでにスコープに入っていると思います。さもなければ、あなたの類推は、スコープ内に '$ x'がない場合、またはグローバル*の場合、基本的に正しい*です。スコープ内に '$ x'という字句がすでにある場合、' for $ x(...){...} 'の動作は変わります。この場合、 'for $ x'はループ内に新しい字句を宣言するので、' do {my $ x; $ x(...){...}}; 'のために。これは、なぜあなたは常に明示的に ''私の$ x'のために書くべきなのかの理由です。 – Schwern

+0

私は現在、Perlの権限が厳密なモードで '$ x(...){...} 'を拒否したり、警告を出すことを決断しなかった理由を考えています。あなたはそれが前に出てきたかどうか知っていますか? –

関連する問題