この広告は、90日以上更新していないブログに表示しています。
404 Blog Not Found:perl - myとourとscopeと
みんな難しく考えすぎです。
(例外については後で考えることにして)とりあえず以下の基本をおさえておけば混乱することはないと思います。
our はグローバル変数をスマートに使用するための宣言です。our は use vars と等価と考えてください。
our$var;
は
use varsqw($var);
と等価。
our$var =1;
は
use varsqw($var);$var =1;
と同じ動きをするもの、と覚えておけばOKです。それ以上の違いは基本的にないと考えて構いません。
ひとつ実用上の違いがあるとすれば、our はPerl 5.6 以降の built-in なのに対して、use vars は vars.pm のPerlスクリプトで実装されています。したがって、use vars よりも our の方が高速でかつスマートです。
ちなみに、最新のPerl5.10でもvars.pmは存在しますので、use vars を使ったプログラムは our に書き換えなくてもそのまま動きます。
vars.pmの実装に興味のある人はこちらの解説もあわせて読んでみてください。→vars
local はグローバル変数の値を一時的にスタック上に退避する命令です。スコープの範囲を外れると元の値が復元されます。
変数の値を実行時のスコープの範囲に限定することができます。
{local$var =2;# foo}は
{local$var;$var =2;# foo}と等価。動作としては
push@{__PACKAGE__.'::$var'}, \$var;# 変数の値をスタックに退避{$var =2;# foo}$var =${pop@{__PACKAGE__.'::$var'}};# スコープを抜けたら復元
という感じで、実行時にブロックで囲まれたスコープを抜けると宣言前の$varの値が復元されます。
(※実際には@{__PACKAGE__.'::$var'}というスタックの場所に値を保存しているわけではありませんが・・・便宜上ということで・・・)
変数の値をスタックに退避する、という動作なので、再帰呼出があってもスタックが許す限り元の値が段階的に復元されます。
追記:ちなみに、変数の値をスタックに退避するタイミングはlocal宣言をした場所なので、本当は
{push@{__PACKAGE__.'::$var'}, \$var;# 変数の値をスタックに退避$var =2;# foo}$var =${pop@{__PACKAGE__.'::$var'}};# スコープを抜けたら復元となるのですが、後で記述するサブルーチンを抜けたときの挙動をわかりやすく説明するため、スコープのちょうど境界外でのスタックの退避・復元と説明とさせてください。
記号を含む特殊変数($_や$/など)はmy宣言できませんが、local宣言はできて、一時的にグローバル変数の値を安全に変更することができます。
local $_; や local $/ = 1; などの用法が Perl5 のプログラムでも使われるのはこのためです。
サブルーチン定義の sub func {}、無名関数 sub {} については、関数を宣言した時ではなく、関数を呼び出した時にブロックのスコープを持つと解釈すればOKです。
サブルーチン内で定義されたlocal変数は、func() の実行が終了すると呼び出し前の値に復元されます。
subfunc{local$var =3;# foo}$ret = func();
は
subfunc{$var =3;# foo}push@{__PACKAGE__.'::$var'}, \$var;$ret = func();$var =${pop@{__PACKAGE__.'::$var'}};
と等価です。
再帰呼出とかクロージャについても特別な解釈はなく、関数が呼び出されるときに変数の値が一時的にスタックに退避され、関数終了時に元の値が復元される、と解釈していれば問題ありません。
ちなみに、サブルーチン内のブロックの中(ifやdoなど)でlocal宣言した場合、そのブロック内でのみ変数のスコープが有効になります。
my はレシキカル変数の宣言です。これで宣言された変数はスコープを外れた場所から参照できなくなります。
既に同じ名前のグローバル変数が定義されている場合、my宣言するとそのスコープ構文の範囲内で有効な(実行時ではない)変数が新しく作られます。
Scope (computer science) - Wikipedia より
$x =0;subf{return$x; }# グロバール変数$xを参照subg{my$x =1;return f(); }# レキシカル変数$xを別に定義→グロバール変数$xには影響しない$f = g();# 関数f()を実行したときのグロバール変数$xの値を返すwarn$f;# 0warn$x;# 0
$x =0;subf{return$x; }# グロバール変数$xを参照subg{local$x =1;return f(); }# グロバール変数$xを一時的に上書き$f = g();# 関数f()を実行したときのグロバール変数$xの値を返すwarn$f;# 1warn$x;# 0
は
$x =0;subf{return$x; }# グロバール変数$xを参照subg{$x =1;return f(); }# グロバール変数$xを上書きpush@{__PACKAGE__.'::$x'}, \$x;# 一時的に$xの値をスタックに退避$f = g();# 関数f()を実行したときのグロバール変数$xの値を返す$x =${pop@{__PACKAGE__.'::$x'}};# $xの値を復元warn$f;# 1warn$x;# 0
と等価である、と解釈することができます。
ダイナミックスコープはスクリプト言語の動きになるので、コンパイル言語に馴染んでいる人にとっては最初に違和感があるかもしれませんが、言語処理系の動作を理解していれば、すぐに慣れると思います。
追記:ちなみに、変数の値をスタックへ退避するタイミングはlocal宣言をした場所となるため、
$x =0;subf{return$x; }# グロバール変数$xを参照subg{push@{__PACKAGE__.'::$x'}, \$x;# 一時的に$xの値をスタックに退避$x =1;# グロバール変数$xを上書きreturn f();}$f = g();# 関数f()を実行したときのグロバール変数$xの値を返す$x =${pop@{__PACKAGE__.'::$x'}};# $xの値を復元warn$f;# 1warn$x;# 0
厳密に書くとこのようなプログラムになります。
単純な{}だけでなく、ifやdoブロック、for、foreach、while、switch、eval{}ブロックなども、そのブロック内で有効なスコープを持ちます。
id:amachangの元記事my と local のサンプル - IT戦記では、
ちなみに do {...} は return の扱いを除いて (sub {...})->() と等価だと考えていいです。まあ、関数をその場で呼び出すようなものですね。
と「関数をその場で呼び出すようなもの」と端折っていますが、ここの仮定が間違っていて、正しくは「一度doブロックを抜けてから関数を呼び出している」という動きになります。
id:amachangのサンプルは書き方が省略されているので見た目上誤解を生みやすいのですが、
our$foo =0;do {local$foo =1;sub{print"$foo\n" }# 0}->();
は
our$foo =0;my$func =do {local$foo =1;sub{print"$foo\n" }};$func->();# 0
と等価であるので、$func->()実行時には既にdoのスコープから外れているのです。
つまり、このlocal宣言はsubではなくdoブロックにかかっているということで、
our$foo =0;push@{__PACKAGE__.'::$foo'}, \$foo;# 退避my$func =do {$foo =1;sub{print"$foo\n" }};$foo =${pop@{__PACKAGE__.'::$foo'}};# 復元$func->();# 0
このように動作していると解釈すれば、すべてがスッキリすると思います。
スコープの有効範囲は、宣言が置かれているブロック{}、eval、またはファイルの末尾までです。
最後にサンプルプログラムを書いて、スコープの有効範囲についてまとめます。
# foo.pl
local$a = -1;warn$a;# -1 <- foo.pl内でlocal宣言して一時的に変数の値を変更する
# main.pl
our$a =0;warn$a;# 0{local$a =1;warn$a;# 1 <- localで一時的にスコープ内に値を限定}warn$a;# 0 <- localは元の値を復元する{eval'$a = 2;';warn$a;# 2 <- eval内で変数の値を変更すると実行後も有効になるeval'local $a = 3;';warn$a;# 2 <- eval内でlocal宣言するとeval後に元の値が復元される}our$a =4;warn$a;# 4 <- 既に宣言された変数にourしても問題ない{our$a =5;warn$a;# 5 <- スコープ内でもう一度ourしても問題ない}warn$a;# 5 <- ただし、ourはスコープを抜けても値を復元しない(localと違う挙動)require'foo.pl';warn$a;# 5 <- foo.plのファイル末尾でスコープは終了したと解釈され元の値を復元する
以上。簡単でしょ?
Quote saved.
Login to quote this blog
Failed to save quote. Please try again later.
You cannot quote because this article is private.