home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

Perl/codepiece/my_and_locals1

Perl/codepiece/my_and_locals1

Perl / codepiece / my_and_locals1
id: 139 所有者: msakamoto-sf    作成日: 2007-03-15 17:34:43
カテゴリ: Perl 

"Effective Perl"(Joseph N.Hall/Randal L.Schwartz, 吉川邦夫訳, アスキー出版局)にmyとlocalの説明が載っていたので、改めで実際に動かし、確認する。


先に結論を。

このコードピースは長い為、先に結論を述べる。

Perlの変数は、スコープの観点から次の二種類がある。

  1. グローバル変数(別名、パッケージ変数) → %(パッケージ名):: でアクセス可能なシンボルテーブルに載る変数
  2. lexical(字句解析的)変数(別名、scoped変数) → ファイル全体, "{}", eval()のスコープを持つ変数

この二つのスコープ間は相互干渉しない。どんなにファイル内でパッケージが切り替わろうとも、myとして宣言された変数はそのファイル内でしかスコープを持たず、そのファイル内であればいかなる場所でもその変数を参照できるし、参照を強制される。

この、ファイル全体にかかるスコープが厄介である。通常は1ファイル1パッケージである為疑問に思うことは少ないが、実験などで一つのファイル内に複数のpackage宣言を混ぜて、いろいろ実験する場合、当惑する遠因となる。packageをスコープの様に考えているのであれば、それは間違いである。

package宣言は現在のパッケージを変更する為のステートメント*1でしかない。確かにpackageはパッケージのシンボルテーブルを利用する為に必要であるが、myは通常のシンボルテーブルとは別の機構で管理されている。package宣言による影響とは何の関係も無い。

myはmyだけのスコーピング機構に従う。それは、ファイル末尾まで > "{}" > eval() の順で定められている。それが全てである。

基本的に全てperldocに書かれている通りでしか動かない。それがPerlの仕様だからだ。しかし、プログラマーがバックグラウンドにC/Java/PHP*2を持っている場合、頭がPerlのスコーピング機構について行けず、知らない間にC/Java/PHPのスコーピングで変数を利用しているという状況になる。

  1. 変数が思った通りの値にならない。
  2. perldocや書籍を読む限りにおいては正しい動作をしているようだ。
  3. なぜそれが正しい動作かがわからない。(頭の中ではC/Java/PHPの動作を無意識に期待しているため)

つまり、正しい動作を説明している記述、それ自体が理解できなくなってしまう。これでは「正しい」変数の宣言と利用は覚束ない。

PerlにはC/Java/PHPでの「ローカル変数」と正対するものは無い、と自分は考えている。シンボルテーブルで参照されるパッケージ変数か、独自のスコーピング機構で管理されるmy/our変数か の二択である。そう割り切った方が、C/Java/PHPの呪縛から抜け出せる。Perlは昔からPerlでしかなく、だからこそ微妙で奇妙に珍妙ながらも絶妙に面白く、好きになってしまった。

今回、my/our/シンボルテーブル、これを理解する為に丸一日使ってこのコードピース群を書いては動かし、確認していった。

結局local迄は到達し得なかった。localと型グロブ、そしてtieも、これがまた、C/Java/PHPを背景に持つ自分にとってはまだまだ理解しきれていない概念である。また折りを見てそれらの壁によじ登ってみたい。

グローバル変数(パッケージのシンボルテーブルとソフトリファレンス)

  • いわゆるグローバル変数を作成する。この場合はデフォルトのmainパッケージのシンボルテーブルに、コンパイル時に登録される。
    • シンボルテーブルへは"%(パッケージ名)::"でアクセスできる。
  • ソフトリファレンスの場合は、そのソフトリファレンスの使用箇所を実行時にシンボルテーブルに追加される。
  • コードピース
#!/usr/bin/perl

print join " ", keys(%::);
print "\n-------------\n";
$compile_time;

${"run_time"};
$$runt_time2;
print join " ", keys(%::);
print "\n";
  • 出力
/ stderr utf8:: " CORE:: DynaLoader:: stdout compile_time attributes::  
                                             ^^^^^^^^^^^^
stdin ARGV INC ENV Regexp:: UNIVERSAL:: $ _<perlio.c main:: - _<perlmain.c 
PerlIO:: _<universal.c 0 @ _<xsutils.c STDOUT IO::  _ + STDERR Internals:: 
STDIN DB:: <none>::
-------------
/ stderr utf8:: " run_time CORE:: DynaLoader:: stdout compile_time runt_time2 
                  ^^^^^^^^                                         ^^^^^^^^^^
attributes::  stdin ARGV INC ENV Regexp:: UNIVERSAL:: $ _<perlio.c main:: - 
_<perlmain.c PerlIO:: _<universal.c 0 @ _<xsutils.c STDOUT IO::  _ + STDERR 
Internals:: STDIN DB:: <none>::
  • 一度目のシンボルテーブル出力時:ソース上、$compile_timeの評価前にシンボルテーブルにアクセスしているが、コンパイル時に組み込まれる為、既にシンボルが存在する。
  • 二度目のシンボルテーブル出力時:ソフトリファレンスの評価後であるため、run_time, run_time2のシンボルが新たに追加されている。
  • 以上より、ソフトリファレンスではないグローバル変数についてはコンパイル時にシンボルテーブルに登録されることが確認できた。

myによる字句解析的(lexical)なスコープ決定

myを用いると、文法に従って(=字句解析的:lexical)スコープが決定される。一組のブレース"{}"、ファイル、eval文字列がスコープとなる。myの宣言箇所から、当該スコープの末尾までがmyの有効範囲となる。

コードピース(1)

myを使用したシンボルは、コンパイル時にはパッケージのシンボルテーブルに配置されるものの、実行時には当該パッケージのシンボルとしてはアクセスできなくなる。

  • コードピース
#!/usr/bin/perl

my $compile_time = 123;
$compile_time = 456;

print join " ", keys(%::);
print "\n----------\n";
print "main::compile_time = [", $main::compile_time, "]\n";
print "::compile_time = [", $::compile_time, "]\n";
print "compile_time = [", $compile_time, "]\n";
  • 出力
/ stderr utf8:: " CORE:: DynaLoader:: stdout compile_time attributes::  
                                             ^^^^^^^^^^^^
stdin ARGV INC ENV Regexp:: UNIVERSAL::  $ _<perlio.c main:: - 
_<perlmain.c PerlIO:: _<universal.c 0 @ _<xsutils.c STDOUT IO::  _ 
+ STDERR Internals:: STDIN DB:: <none>::
----------
main::compile_time = [] <<<< シンボルテーブルには存在するのにアクセスできない。
::compile_time = [] <<<< (同上)
compile_time = [456]

コードピース(2)

コードピース(1)でmyの順番を入れ替えてみると、シンボルにアクセスする時のパッケージ名によるスコーピング*3が有効になる。

  • コードピース
#!/usr/bin/perl

$compile_time = 456;
my $compile_time = 123;

print join " ", keys(%main::);
print "\n----------\n";
print "main::compile_time = [", $main::compile_time, "]\n";
print "::compile_time = [", $::compile_time, "]\n";
print "compile_time = [", $compile_time, "]\n";
  • 出力
/ stderr utf8:: " CORE:: DynaLoader:: stdout compile_time attributes::  
                                             ^^^^^^^^^^^^
stdin ARGV INC ENV Regexp:: UNIVERSAL::  $ _<perlio.c main:: - 
_<perlmain.c PerlIO:: _<universal.c 0 @ _<xsutils.c STDOUT IO::  _ 
+ STDERR Internals:: STDIN DB:: <none>::
----------
main::compile_time = [456] <<<< mainパッケージのシンボルテーブルにアクセスできている。
::compile_time = [456]
compile_time = [123] <<<< パッケージ名が無い場合、myでスコーピングされたシンボルにアクセスしに行っている?

この辺りになると大分混沌としてくる。シンボルテーブルとの絡みで、my とシンボルの管理が教科書通りにはなってないのではないのか?とも思えてくる。

そこで、数時間の調査と、try&errorの末、より明確にグローバル変数アクセス、my、そしてPerl5.6以降で使えるour、さらにパッケージ名完全指定時の変数アクセスのかなり正確な処理が追えるようになった。

大量のtry&errorの果てに。

個人がまとめた言語のリファレンス系記事を読んでいて時々感じるのは、言語特有の壁に相当する部分が、非常に教科書的、理論的に書かれている点である。確かにプログラミング言語は理論的に動くものなので、リファレンスとしてはその通りに書いて良いのだが、しかし、記述漏れは有る。また、サンプルも「こんなときどうなのよ?」といったIFの記述が不足していることが多い。

恐らく、大量のtry&errorの果てに、結局は仕様書通りであるからこそそうした記述になると思われる。しかし、そこに至るまでの試行の中で、教科書には載っていない事象も発生したはずである。もちろんそうした事象の原因を突き詰めていくと、教科書に書かれているとおりに動いていたことになるわけであるが、しかし、理論だけ学んで実践ができるのであれば誰も苦労はしない。

事象を見て、その裏側で動いている理論を追跡できるようになるには、理論と実際をマッピングするための知識・経験・直感が必要であり、そしてそれは経験を積むほか無いであろう。直感も経験の一つにはいるし、知識は定着させるのに経験が必要であるからだ。

以下に続くのは、率直に言っていかなるサンプルにもなり得ないコードピース群である。しかし、言語のシンボルの取り扱いという、中枢レベルのテーマを扱うには仕様を疑ってかかるほどの愚かしいtry&errorが欠かせないことを自分はPHPより学んでいる*4
よって、敢えて自分の辿った膨大な無駄で愚かしく自明なものをわざわざ再確認するようなコードをここに載せ、辿った道のりを残しておく。

コードピース (1) : グローバル変数は何度代入してもメモリ上同じ位置か?

まず、グローバル変数は何度代入しても同じメモリ上であることを確認したい。

  • コードピース
#!/usr/bin/perl

$var1 = "abc";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";

$var1 = "def";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
  • 出力
$ ./hoge.pl
$var1 = abc
\$var1 = SCALAR(0x8164460)
$var1 = def
\$var1 = SCALAR(0x8164460)

特に常識と異なる点はない。

コードピース (2) : my変数は何度代入してもメモリ上同じ位置か?

my宣言した変数を確認する。

  • コードピース
#!/usr/bin/perl

my $var1;
$var1 = "abc";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
$var1 = "def";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";

my $var1;
$var1 = "ghi";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
$var1 = "jkl";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
  • 出力
$ ./test1.pl
$var1 = abc
\$var1 = SCALAR(0x8164450)
$var1 = def
\$var1 = SCALAR(0x8164450)
$var1 = ghi
\$var1 = SCALAR(0x8164504) <<<< ここから2番目のmy宣言以降
$var1 = jkl
\$var1 = SCALAR(0x8164504)

2番目のmy宣言により、アドレスが変わる=新しい領域がとられている点に注意したい。

myは、変数を作ってしまうのだ。

コードピース (3) : my 宣言したものはシンボルテーブル(%<package>::)からアクセスできないか?

冒頭の方の実験では、my宣言した変数名と、グローバル変数名を同じにしていた為、シンボルテーブルにある変数名が本当にグローバル変数のものか確証が採れなかった。

  • コードピース
#!/usr/bin/perl

$var1;     # "グローバル"または"パッケージ"変数
my $var2;  # "字句解析的(lexical)"または"スコープ"変数

print join " ", keys(%::);
print "\n";
print '\$var1 = ', \$var1, "\n";
print '\$var2 = ', \$var2, "\n";
  • 出力
$ ./test1.pl
/ stderr utf8:: " CORE:: DynaLoader:: stdout attributes::  stdin 
ARGV INC ENV Regexp:: UNIVERSAL:: $ _<perlio.c main:: - _<perlmain.c 
PerlIO:: _<universal.c 0 @ _<xsutils.c var1 STDOUT IO::  _ + 
                                       ^^^^
STDERR Internals:: STDIN DB:: <none>::
\$var1 = SCALAR(0x8164468)
\$var2 = SCALAR(0x8164450)

こちらも教科書通り、my 宣言された変数はシンボルテーブルには載らないことが確認できた。

コードピース (4) : package宣言でグローバル変数はどうなるか?

パッケージ名を指定しないでグローバル変数を扱うとき、package宣言をするだけで本当に切り替わるのか?

  • コードピース
#!/usr/bin/perl

print "package=[", __PACKAGE__, "]\n";
print '%:: = ', join(" ", keys(%::)), "\n";
print '%hoge:: = ', join(" ", keys(%hoge::)), "\n";

$var1 = "abc";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$hoge::var1 = ', $hoge::var1, "\n";
print '\$hoge::var1 = ', \$hoge::var1, "\n";

package hoge;
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$hoge::var1 = ', $hoge::var1, "\n";
print '\$hoge::var1 = ', \$hoge::var1, "\n";

$var1 = "def";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$hoge::var1 = ', $hoge::var1, "\n";
print '\$hoge::var1 = ', \$hoge::var1, "\n";

package main;
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$hoge::var1 = ', $hoge::var1, "\n";
print '\$hoge::var1 = ', \$hoge::var1, "\n";
  • 出力
package=[main]
%:: = ... var1 ...
%hoge:: = var1
$var1 = abc
\$var1 = SCALAR(0x8168e10)
$hoge::var1 =                     <<< defined()を確認してみた結果、hogeパッケージのvar1は、undef。
\$hoge::var1 = SCALAR(0x8168e88)

package=[hoge]
$var1 =
\$var1 = SCALAR(0x8168e88)        <<< package宣言直後、デフォルトパッケージがhogeに切り替わった為、
$hoge::var1 =                         アドレスもhoge::var1と同一になる。
\$hoge::var1 = SCALAR(0x8168e88)

$var1 = def
\$var1 = SCALAR(0x8168e88)        <<< 代入後、予想された通り。
$hoge::var1 = def
\$hoge::var1 = SCALAR(0x8168e88)

package=[main]
$var1 = abc                       <<< mainパッケージの戻ってみると、デフォルトパッケージがmain
\$var1 = SCALAR(0x8168e10)            に戻るので、最初に代入した値が取得できている。
$hoge::var1 = def
\$hoge::var1 = SCALAR(0x8168e88)

これも、教科書通りの動きであることを確認できた。

コードピース (5) : 同じファイル中で、パッケージを切り替えてmyをするとどうなるか。

見出しでは分かりづらいと思われるので、コードピースを参照。

  • コードピース
#!/usr/bin/perl

print "package=[", __PACKAGE__, "]\n";
print '%:: = ', join(" ", keys(%::)), "\n";
print '%hoge:: = ', join(" ", keys(%hoge::)), "\n";

my $var1;
$var1 = "abc";

$var1 = "abc";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";

package hoge;
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";

$var1 = "def";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";

package main;
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
  • 出力
package=[main]
%:: = ... <<<< 当然、var1はシンボルテーブルには存在しない。
%hoge:: = <<<< 同上
$var1 = abc
\$var1 = SCALAR(0x8168ca4)
package=[hoge]              <<<< hogeパッケージに切り替わっても、myされた変数のアドレス・値は一緒。
$var1 = abc
\$var1 = SCALAR(0x8168ca4)
$var1 = def
\$var1 = SCALAR(0x8168ca4)
package=[main]
$var1 = def
\$var1 = SCALAR(0x8168ca4)

教科書通りである。ただし、殆どのサイトではこうした、一つのファイルに複数のパッケージが入り乱れたケースを取り扱っていない。1ファイル1パッケージが通常であるが、それだけで例を挙げていると、あまり深く考えずに雰囲気だけで判断する自分のような人間は、見た目にだまされて、package後にmyすればパッケージ変数になると思ってしまう。

人間は理論をそのまま覚えることはできないので、正常形の理論通りではなく、ELSEの理論通りも掲示しないと覚えられないのではないか。

とにもかくにも、どうやら、教科書通り、my宣言とpackageは何の関係もないことが分かった。そう、myのスコープにはpackageの範囲は含まれていない、関与していないのだ。

コードピース (6) : 多段のbrace("{}")の中でも同名のmy宣言を行うとどうなる?

  • コードピース
#!/usr/bin/perl
my $var1;
$var1 = "abc";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
{
    my $var1;
    $var1 = "def";
    print '$var1 = ', $var1, "\n";
    print '\$var1 = ', \$var1, "\n";
    {
        my $var1;
        $var1 = "ghi";
        print '$var1 = ', $var1, "\n";
        print '\$var1 = ', \$var1, "\n";
    }
    print '$var1 = ', $var1, "\n";
    print '\$var1 = ', \$var1, "\n";
}
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
  • 出力
$var1 = abc
\$var1 = SCALAR(0x8164450)
$var1 = def                  <<<< 1段目の{}、アドレスが変わっている以上、別の変数実体。
\$var1 = SCALAR(0x81644b0)
$var1 = ghi                  <<<< 2段目の{}、同上。
\$var1 = SCALAR(0x816451c)
$var1 = def                   <<<< 1段目の{}に戻ると、1段目のmyにアクセスできている。
\$var1 = SCALAR(0x81644b0)
$var1 = abc                  <<<< mainに戻ると、main側のmyにアクセスできている。
\$var1 = SCALAR(0x8164450)

これはあまり見かけない例ではある。この様に、入れ子になっていても、都度myを行うことで、その{}の範囲で変数を取り扱えるようになる。

では、myをしなければどうなるか?もしエラーにならなければどうなるのか?

コードピース (7) : 多段のbrace("{}")の中でmy宣言を行わないとどうなる?

  • コードピース
#!/usr/bin/perl
use strict;   <<< もしbrace"{}"毎に区切られてしまうのであれば、myが無いbraceでエラーになるはずである。
my $var1;
$var1 = "abc";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
{
    $var1;
    $var1 = "def";
    print '$var1 = ', $var1, "\n";
    print '\$var1 = ', \$var1, "\n";
    {
        $var1;
        $var1 = "ghi";
        print '$var1 = ', $var1, "\n";
        print '\$var1 = ', \$var1, "\n";
    }
    print '$var1 = ', $var1, "\n";
    print '\$var1 = ', \$var1, "\n";
}
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
  • 出力
$var1 = abc
\$var1 = SCALAR(0x8164450)
$var1 = def
\$var1 = SCALAR(0x8164450)   <<<< {}の中に入ってもアドレスが同じ。
$var1 = ghi
\$var1 = SCALAR(0x8164450)
$var1 = ghi                  <<<< {}を抜けても、中で代入した値が保持される。
\$var1 = SCALAR(0x8164450)
$var1 = ghi
\$var1 = SCALAR(0x8164450)

これも、言われてみれば確かに教科書通りではあるが、コードピース (6) の結果も踏まえないと頭の整理が難しい。自分の場合、CやJavaで育ったせいか、何となく、感覚的に受け入れるのに時間がかかった。

結局、変数の確保されているアドレスに相当する値をデバッグできなければ頭で納得できない様である。

コードピース (8) : lexical変数のスコープの一つに、ファイルの終わりまでとあるが本当か?

パッケージでファイルを分割し、同じ名前のグローバル変数とmy宣言を切り、細かく見ていく。

  • コードピース (長くなってきたので、mainパッケージにおいては出力内容をコメントして包括する。)
    • test2.pm
package test2;
$var1 = 123;
my $var2 = 456;
sub test {
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$var2 = ', $var2, "\n";
print '\$var2 = ', \$var2, "\n";
}
1;
    • test.pl (mainパッケージ)
#!/usr/bin/perl
use test2;    <<<< この段階でtest2.pmが評価される。
print '%:: = ', join(" ", keys(%::)), "\n";
print '%test2:: = ', join(" ", keys(%test2::)), "\n";
# 出力:
# %:: = ... var1 ...
# %test2:: = test var1 import

$var1 = "abc";
my $var2 = "def";
&main::test;
&test2::test;
# 出力:
# package=[main]             <<<< それぞれ、mainパッケージの/test2パッケージの変数を参照できている。
# $var1 = abc
# \$var1 = SCALAR(0x8168e10)
# $var2 = def
# \$var2 = SCALAR(0x8164504)
# package=[test2]
# $var1 = 123
# \$var1 = SCALAR(0x8168c9c)
# $var2 = 456
# \$var2 = SCALAR(0x8168ca8)

$var1 = "ghi";              <<<< $var1はデフォルトパッケージのmain側になるはず。
$var2 = "jkl";              <<<< 本当にmyのスコープがファイル終端までであれば、
&main::test;                     &test2::testでは、別ファイルにあるので影響が無い筈。
&test2::test;
# 出力:
# package=[main]
# $var1 = ghi
# \$var1 = SCALAR(0x8168e10)
# $var2 = jkl
# \$var2 = SCALAR(0x8164504) <<<< mainパッケージが変更されているのは教科書通り。
# package=[test2]
# $var1 = 123
# \$var1 = SCALAR(0x8168c9c)
# $var2 = 456
# \$var2 = SCALAR(0x8168ca8) <<<< test2パッケージの内容は変化無し。これも教科書通り。

package test2;               <<<< 一時的にtest2パッケージに切り替える。
$var1 = 789;                      $var1の方はtest2のデフォルトパッケージを参照するが、
$var2 = 321;                      lexicalであるvar2はあくまでも物理的なファイル終端
&main::test;                      に束縛される為、ここでのvar2は test.pl のvar2であり、
&test2::test;                     test2.pm のvar2では無いはず。
# 出力:
# package=[main]
# $var1 = ghi
# \$var1 = SCALAR(0x8168e10)
# $var2 = 321
# \$var2 = SCALAR(0x8164504)  <<<< mainのvar2が変更されている。教科書通り。
# package=[test2]
# $var1 = 789                 <<<< var1はtest2側が変更されている。教科書通り。
# \$var1 = SCALAR(0x8168c9c)
# $var2 = 456
# \$var2 = SCALAR(0x8168ca8)  <<<< test2側のvar2は変更されていない。教科書通り。

package main;                 <<<< パッケージをmainに切り替えても問題ないことを確認する。
&main::test;
&test2::test;
# 出力:                      <<<< 直前の状態と変化しないことが確認できた。
# package=[main]
# $var1 = ghi
# \$var1 = SCALAR(0x8168e10)
# $var2 = 321
# \$var2 = SCALAR(0x8164504)
# package=[test2]
# $var1 = 789
# \$var1 = SCALAR(0x8168c9c)
# $var2 = 456
# \$var2 = SCALAR(0x8168ca8)

sub test {
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$var2 = ', $var2, "\n";
print '\$var2 = ', \$var2, "\n";
}

以上より、my 宣言された変数はファイルスコープに束縛されるのであり、package; には束縛されない事を改めて確認できた。

コードピース (9) : lexical変数をパッケージ名をつけてアクセスしても、ちゃんと無意味になるか?

lexical変数はパッケージのシンボルテーブルには載らない。従って、他のファイルに存在するからといって、そのファイル中のmy変数に対して、外部パッケージからパッケージ名付きでアクセスしても無意味なはずである。

→コンパイル時に、パッケージのシンボルテーブルにundefでシンボルが載ってしまう筈である。

  • コードピース
    • test2.pm
package test2;
my $var1 = 456;
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
1;
    • test.pl
#!/usr/bin/perl
use strict;
use test2;
print '%:: = ', join(" ", keys(%::)), "\n";
print '%test2:: = ', join(" ", keys(%test2::)), "\n";

my $var1 = 123;

print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$test2::var1 = ', $test2::var1, "\n";
print '\$test2::var1 = ', \$test2::var1, "\n";
  • 出力:
package=[test2]            <<<< test2.pm がuseされたときに実行されたコード
$var1 = 456
\$var1 = SCALAR(0x816e148)
 
%:: = ... test2:: ...      <<<< 当然、myなのでvar1のシンボルはmainシンボルテーブルに無い。
%test2:: = var1 import     <<<< test2.pmについては、test.pl中でアクセスしてしまっているので、
                                undefでシンボルが入ってしまっている。
package=[main]
$var1 = 123
\$var1 = SCALAR(0x8168ffc)
$test2::var1 =
\$test2::var1 = SCALAR(0x816e1e4)  <<<< 当然、冒頭の出力とは異なる。

コードピース (10) : lexical変数は本当にファイルスコープか?その2

きわめて変則的な例だが、パッケージのファイルとは別のファイルに、そのパッケージのメソッドがあるような場合など。
何を言っているのか意味不明なので、コードピースをご覧になられたほうが早い。

  • コードピース
    • test2.pm
package test2;
my $var1 = 456;
sub func1 {
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
}
1;
    • test.pl
#!/usr/bin/perl
use strict;
use test2;
print '%:: = ', join(" ", keys(%::)), "\n";
print '%test2:: = ', join(" ", keys(%test2::)), "\n";

my $var1 = 123;
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";

package test2;                         <<<< test2.pmとは別ファイルで、test2パッケージにサブルーチンを追加。
sub func2 {
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";         <<<< この中でvar1を参照する。本当にmy宣言がpackageと無関係であれば、
print '\$var1 = ', \$var1, "\n";            test2.pmの $var1 = 456 ではなく、このファイルの $var1 になるはず。
}

package main;
$var1 = 789;
&test2::func1;
&test2::func2;
  • 出力:
%:: = ... test2:: ...          <<<< 当然lexical変数なので、var1はmainにもtest2にも、シンボルテーブルに存在しない。
%test2:: = func1 func2 import
package=[main]                 <<<< mainでの my $var1 を参照。
$var1 = 123
\$var1 = SCALAR(0x816905c)
package=[test2]                <<<< test2.pmで定義されているサブルーチンを実行すれば、当然、test2.pm
$var1 = 456                         中の my $var1 = 456 が参照される。
\$var1 = SCALAR(0x816e1a8)
package=[test2]                <<<< test.plで定義されているサブルーチンを実行。test2.pmではなく
$var1 = 789                         test.pl側の、変更された $var = 789 が参照されている。 
\$var1 = SCALAR(0x816905c)

以上より、my 宣言のスコープはpackageとは全く関係ないことが確実に判明した。

コードピース (11) : グローバル変数とlexical変数名がかぶったら?

冒頭のコードピースに対するリベンジである。変数名がかぶった場合、myが先か否かでどう変数の実体割り当てが変化するか、再度確認してみる。

  • コードピース
#!/usr/bin/perl
print '%:: = ', join(" ", keys(%::)), "\n";

my $var1 = 123;                               <<<< my 宣言が先の場合。グローバル変数はどうなるか?
$var1 = "abc";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$main::var1 = ', $main::var1, "\n";
print '\$main::var1 = ', \$main::var1, "\n";
print '$main::var1 is undef', "\n" if !defined($main::var1);
^ <<<<<< ソースコード中で参照されている以上、コンパイル時にシンボル自体はシンボルテーブルに
         載るはず。但し、中身はundefになるのではないか?

$var2 = 456;   <<<< my 宣言が後の場合。グローバル変数は正常に参照可能?
my $var2 = "def";
print '$var2 = ', $var2, "\n";
print '\$var2 = ', \$var2, "\n";
print '$main::var2 = ', $main::var2, "\n";
print '\$main::var2 = ', \$main::var2, "\n";
  • 出力:
%:: = ... var2 ... var1 ...
$var1 = abc
\$var1 = SCALAR(0x8164498)
$main::var1 =
\$main::var1 = SCALAR(0x8168eec)
$main::var1 is undef               <<<< やはり、シンボルテーブルにはあるがundefになっている。

$var2 = def                        <<<< 予想通り、グローバル変数も正常に参照できている。
\$var2 = SCALAR(0x8168f70)
$main::var2 = 456
\$main::var2 = SCALAR(0x8168f64)

グローバル変数とlexical変数で同名のものがかぶってしまった場合、先にlexical変数として登録済か判断しているのかもしれない。登録済であればそちらを参照し、登録されていなければ、そこで初めて、現在のパッケージのシンボルテーブルを参照するのかもしれない。

いずれにせよ、これで冒頭のコードピースに対して抱いた謎は解けた。

コードピース (12) : ourについて - 1

my がシンボルテーブルと独立した名前空間と実体を提供するのに対し、our はシンボルテーブルをグローバル変数に見せかける(bindする)ために使われているようである。
また、ourは実体を作成しない(パッケージのシンボルテーブルをルックアップしているだけ?)ため、何度呼んでも同じ変数を参照できる。

  • コードピース
#!/usr/bin/perl
use 5.006;
print '%:: = ', join(" ", keys(%::)), "\n";

our $var1;
$var1 = "abc";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$main::var1 = ', $main::var1, "\n";
print '\$main::var1 = ', \$main::var1, "\n";

our $var1;
$var1 = "def";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$main::var1 = ', $main::var1, "\n";
print '\$main::var1 = ', \$main::var1, "\n";

$main::var1 = "ghi";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$main::var1 = ', $main::var1, "\n";
print '\$main::var1 = ', \$main::var1, "\n";
  • 出力:
%:: = ... var1 ...

$var1 = abc
\$var1 = SCALAR(0x81644f8)
$main::var1 = abc                  <<<< パッケージのシンボルテーブルにもちゃんと値が入る。
\$main::var1 = SCALAR(0x81644f8)        アドレスも同じところを参照している。

$var1 = def                        <<<< 二回呼んでも同じ場所を参照できる。
\$var1 = SCALAR(0x81644f8)
$main::var1 = def                  <<<< 片方を変更すれば、もう片方も変わる。
\$main::var1 = SCALAR(0x81644f8)

$var1 = ghi
\$var1 = SCALAR(0x81644f8)
$main::var1 = ghi                  <<<< 片方を変更すれば、もう片方も変わる。
\$main::var1 = SCALAR(0x81644f8)

教科書通りの動きであることを確認できた。

コードピース (13) : ourについて - 2

our自体のスコープはmyと変わらない。同一ファイル中でパッケージが切り替わっても、元のパッケージのシンボルを参照できることを確認する。

  • コードピース (出力も織り交ぜる。)
#!/usr/bin/perl
use 5.006;
print '%:: = ', join(" ", keys(%::)), "\n";
print '%hoge:: = ', join(" ", keys(%hoge::)), "\n";
# 出力:
# %:: = .. var1 ...
# %hoge:: = var1

our $var1;
$var1 = "abc";
# ダンプ出力:                     <<<< main::var1はourでbindされているが、hoge::var1は
# $var1 = abc                           undef。
# \$var1 = SCALAR(0x816957c)
# $main::var1 = abc
# \$main::var1 = SCALAR(0x816957c)
# $hoge::var1 =
# \$hoge::var1 = SCALAR(0x816963c)

package hoge;
# ダンプ出力:                     <<<< 現在パッケージが切り替わっても、our宣言された$var1は変わらず
# $var1 = abc                           mainパッケージの内容を参照できる。
# \$var1 = SCALAR(0x816957c)
# $main::var1 = abc
# \$main::var1 = SCALAR(0x816957c)
# $hoge::var1 =
# \$hoge::var1 = SCALAR(0x816963c)

$var1 = "def";
# ダンプ出力:                     <<<< hogeパッケージないでも、ourのスコープ内なので、mainパッケージを
# $var1 = def                           参照している。
# \$var1 = SCALAR(0x816957c)
# $main::var1 = def
# \$main::var1 = SCALAR(0x816957c)
# $hoge::var1 =
# \$hoge::var1 = SCALAR(0x816963c)

$hoge::var1 = 123;
# ダンプ出力:                     <<<< パッケージ名指定でようやく値が設定される。
# $var1 = def
# \$var1 = SCALAR(0x816957c)
# $main::var1 = def
# \$main::var1 = SCALAR(0x816957c)
# $hoge::var1 = 123
# \$hoge::var1 = SCALAR(0x816963c)

package main;
# ダンプ出力:                     <<<< mainパッケージに戻っても、変わらず。
# $var1 = def
# \$var1 = SCALAR(0x816957c)
# $main::var1 = def
# \$main::var1 = SCALAR(0x816957c)
# $hoge::var1 = 123
# \$hoge::var1 = SCALAR(0x816963c)

教科書通りである。

コードピース (14) : ourについて(同じファイル中で、パッケージを異にして二回呼ぶ) - 3

コードピース(13)で、もしpackage hoge; に続いてourを使用したらどうなるのか?$var1が、$hoge::var1にバインドされるのではないか?

  • コードピース (出力も織り交ぜる。)
#!/usr/bin/perl
use 5.006;
print '%:: = ', join(" ", keys(%::)), "\n";
print '%hoge:: = ', join(" ", keys(%hoge::)), "\n";
# 出力:
# %:: = .. var1 ...
# %hoge:: = var1

our $var1;
$var1 = "abc";

package hoge;
# ダンプ出力:                     <<<< この時点では、$var1は$main::var1にバインドされている。
# $var1 = abc
# \$var1 = SCALAR(0x8169584)
# $main::var1 = abc
# \$main::var1 = SCALAR(0x8169584)
# $hoge::var1 =
# \$hoge::var1 = SCALAR(0x8169644)

our $var1 = "def";
# ダンプ出力:                     <<<< バインド先が$hoge::var1切り替わっている。
# $var1 = def
# \$var1 = SCALAR(0x8169644)
# $main::var1 = abc
# \$main::var1 = SCALAR(0x8169584)
# $hoge::var1 = def
# \$hoge::var1 = SCALAR(0x8169644)

$hoge::var1 = 123;
# ダンプ出力:                     <<<< $hoge::var1を変更すれば、bindしている$var1も連動する。
# $var1 = 123
# \$var1 = SCALAR(0x8169644)
# $main::var1 = abc
# \$main::var1 = SCALAR(0x8169584)
# $hoge::var1 = 123
# \$hoge::var1 = SCALAR(0x8169644)

package main;
# ダンプ出力:                     <<<< mainパッケージに戻っても、$var1は$hoge::var1にbindしている。
# $var1 = 123
# \$var1 = SCALAR(0x8169644)
# $main::var1 = abc
# \$main::var1 = SCALAR(0x8169584)
# $hoge::var1 = 123
# \$hoge::var1 = SCALAR(0x8169644)

教科書通りであるが、こういった形で使用することは殆ど無いと思われる。

コードピース (15) : ourについて - lexcal変数と同じく、ファイルが異なれば参照不可を確認

パッケージAからパッケージBの変数にアクセスするには、パッケージBのシンボルテーブルに対象の変数が載っていなければならない。
スペルミスを防ぐ為、use strict 機能を使用すると、下記のコードで "Global symbol "$pkg_global" requires explicit package name" が発生し、コンパイルエラーとなる。

package pkgA;
$pkg_global = 123;

この対策として、5.6より前は use vars, 5.6以降はourが使えるようになっている。

→上記記述は、誤解を含んでいた。パッケージBのシンボルテーブルの変数を、パッケージA上から普通にアクセスするにはourを使う必要があると、混乱していた。

ourのperldoc( http://perldoc.perl.org/functions/our.html )を見ても分かるとおり、ourはパッケージのシンボルテーブル上の変数を、lexical変数と同じスコープでパッケージ名を省略できるようにしているだけである。
従って、コードピース(14)の場合は、同じファイル中なので、当然パッケージAでourを使っていれば、ファイル中の後ろに続くパッケージBでも、同じ変数を参照できる。逆に下記に示すコードピースの場合、ファイルが異なるため、当然 "package test2" でourしているvar1は、"package test1"からはパッケージ名を省略して参照することはできない。

モジュールを使用するに当たり、もっともよく使うであろうパターンの動作を確認してみる。

  • コードピース
    • test2.pm
package test2;
our $var1 = 123;
sub func {
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
}
1;
    • test.pl
#!/usr/bin/perl
use 5.006;
use test2;
print '%:: = ', join(" ", keys(%::)), "\n";
print '%test2:: = ', join(" ", keys(%test2::)), "\n";

our $var1;
$var1 = "abc";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$main::var1 = ', $main::var1, "\n";
print '\$main::var1 = ', \$main::var1, "\n";
&test2::func;

$test2::var1 = 456;

print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
print '$main::var1 = ', $main::var1, "\n";
print '\$main::var1 = ', \$main::var1, "\n";
&test2::func;
  • 出力:
%:: = ... var1 ...
%test2:: = var1 func import

$var1 = abc                       <<<< 初期状態
\$var1 = SCALAR(0x81691c0)
$main::var1 = abc
\$main::var1 = SCALAR(0x81691c0)
package=[test2]
$var1 = 123
\$var1 = SCALAR(0x81690ac)

$var1 = abc                       <<<< $test2::var1更新後。
\$var1 = SCALAR(0x81691c0)             mainパッケージは変化がないが、test2パッケージのvar1
$main::var1 = abc                      は確実に更新されている。
\$main::var1 = SCALAR(0x81691c0)
package=[test2]
$var1 = 456
\$var1 = SCALAR(0x81690ac)

これも、教科書通りの動きとなった。ourはlexical変数と同じスコープになる為、同じ名前の変数をourしていても、ファイルが異なれば異なる変数実体にbindする。

コードピース (16) : use vars

これはPerl5.6より前の機能である為、それほど突っ込まない。

コードピース (15)でも述べたが、use strict を有効化すると、パッケージ変数を書くのが面倒くさくなる。

package pkgA;
use strict;

$pkg_global = 123; <<<< コンパイルエラー。
$pkgA::pkg_global = 123; <<<< セーフ。

これに対処する為のプラグマ(pragma)が "use vars" である。コードピース(13)のtest2.pmを、use vars に直してみる。

  • コードピース
    • test2.pm
package test2;
use strict;
use vars qw($var1);
$var1 = 123;
sub func {
print "package=[", __PACKAGE__, "]\n";
print '$var1 = ', $var1, "\n";
print '\$var1 = ', \$var1, "\n";
}
1;
    • test.pl
#!/usr/bin/perl
use test2;
print '%test2:: = ', join(" ", keys(%test2::)), "\n";
&test2::func;
$test2::var1 = 456;
&test2::func;
  • 出力:
%test2:: = var1 BEGIN func import
package=[test2]
$var1 = 123
\$var1 = SCALAR(0x819e82c)
package=[test2]
$var1 = 456
\$var1 = SCALAR(0x819e82c)

ourを使用したときと同じであることを確認できた。

コードピース (17) : とりあえずのまとめ

本節のコードピース(1) - (16)は、実は最初からその順にtry&errorしていったわけではない。最初、冒頭でmyの順番を逆にしたりしていたときに、どうしても教科書通りの説明だけでは納得いかなくなり、"参照"で示したURLを参照したり、何度か試行錯誤した。そして次のコードにtryした結果、漸く靄が晴れた。その後、頭で結実したロジックを一個一個確認していくためにコードピース(1) - (16)を順次試していった。

自分はCからプログラミングに入っていった為、シンボル管理についてはどうしても、メモリアドレスと結びつかないと理解できない頭になってしまったようである*5。思い切って、リファレンスのアドレスを表示させてみたのが幸いした。

自分も含めて、もしシンボルテーブル・my・ourの区別があやふやになった場合は、以下のコードピースで、パッケージの切り替えや変数への代入・4種類の変数内容の動きをoutputと合わせてじっくり眺めて欲しい。

  • コードピース:出力を織り込む。
#!/usr/bin/perl

$var1 = "in_main";
my $var2 = "in_main";
our $var3 = "in_main";
$hoge::var4 = "in_main";
# >>>> output:
(var1, main::var1, hoge::var1) = (in_main, in_main, )
(\var1, \main::var1, \hoge::var1) = (SCALAR(0x18242fc), SCALAR(0x18242fc), SCALAR(0x182d524))
(var2, main::var2, hoge::var2) = (in_main, , )
(\var2, \main::var2, \hoge::var2) = (SCALAR(0x18242e4), SCALAR(0x182d5d8), SCALAR(0x182d614))
(var3, main::var3, hoge::var3) = (in_main, in_main, )
(\var3, \main::var3, \hoge::var3) = (SCALAR(0x182435c), SCALAR(0x182435c), SCALAR(0x182d6ec))
(var4, main::var4, hoge::var4) = (, , in_main)
(\var4, \main::var4, \hoge::var4) = (SCALAR(0x182d77c), SCALAR(0x182d77c), SCALAR(0x18243a4))
# <<<<

package hoge;
# >>>> output:
(var1, main::var1, hoge::var1) = (, in_main, )
(\var1, \main::var1, \hoge::var1) = (SCALAR(0x182d524), SCALAR(0x18242fc), SCALAR(0x182d524))
(var2, main::var2, hoge::var2) = (in_main, , )
(\var2, \main::var2, \hoge::var2) = (SCALAR(0x18242e4), SCALAR(0x182d5d8), SCALAR(0x182d614))
(var3, main::var3, hoge::var3) = (in_main, in_main, )
(\var3, \main::var3, \hoge::var3) = (SCALAR(0x182435c), SCALAR(0x182435c), SCALAR(0x182d6ec))
(var4, main::var4, hoge::var4) = (in_main, , in_main)
(\var4, \main::var4, \hoge::var4) = (SCALAR(0x18243a4), SCALAR(0x182d77c), SCALAR(0x18243a4))
# <<<<

$var1 = "in_hoge";
my $var2 = "in_hoge";
our $var3 = "in_hoge";
$hoge::var4 = "in_hoge";
# >>>> output:
(\var1, \main::var1, \hoge::var1) = (SCALAR(0x182d524), SCALAR(0x18242fc), SCALAR(0x182d524))
(var2, main::var2, hoge::var2) = (in_hoge, , )
(\var2, \main::var2, \hoge::var2) = (SCALAR(0x1830db0), SCALAR(0x182d5d8), SCALAR(0x182d614))
(var3, main::var3, hoge::var3) = (in_hoge, in_main, in_hoge)
(\var3, \main::var3, \hoge::var3) = (SCALAR(0x182d6ec), SCALAR(0x182435c), SCALAR(0x182d6ec))
(var4, main::var4, hoge::var4) = (in_hoge, , in_hoge)
(\var4, \main::var4, \hoge::var4) = (SCALAR(0x18243a4), SCALAR(0x182d77c), SCALAR(0x18243a4))
# <<<<

package main;
# >>>> output:
(var1, main::var1, hoge::var1) = (in_main, in_main, in_hoge)
(\var1, \main::var1, \hoge::var1) = (SCALAR(0x18242fc), SCALAR(0x18242fc), SCALAR(0x182d524))
(var2, main::var2, hoge::var2) = (in_hoge, , )
(\var2, \main::var2, \hoge::var2) = (SCALAR(0x1830db0), SCALAR(0x182d5d8), SCALAR(0x182d614))
(var3, main::var3, hoge::var3) = (in_hoge, in_main, in_hoge)
(\var3, \main::var3, \hoge::var3) = (SCALAR(0x182d6ec), SCALAR(0x182435c), SCALAR(0x182d6ec))
(var4, main::var4, hoge::var4) = (, , in_hoge)
(\var4, \main::var4, \hoge::var4) = (SCALAR(0x182d77c), SCALAR(0x182d77c), SCALAR(0x18243a4))
# <<<<

参考URL [#k0d79c00]


*1: 本当にこれで正鵠か否かは調査が必要だが、ここでの文脈に限って言えばこれで正しいと思う。
*2: 自分のことである。
*3: この呼び名であっているのか?
*4: シンボルの取り扱いの癖の強さは、特にLLに顕著だと思う。
*5: PHPはこのための関数が無いから辛い・・・。xdebugにも無いんだもの。

プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2008-12-29 17:44:54
md5:fa5ed0cb37c8e7d61556996f02c5d6d2
sha1:0ff55a5a11f74cfbfb7dc385343814be71e2f917
コメント
コメントを投稿するにはログインして下さい。