名前のない配列のmapに関する世にも奇妙な物語
perlの中で便利だけどちょっぴりなものに$_があります。普通のグローバル変数とも振る舞いが違っていて、
sub foo { print "$_\n"; map { foo; } (0..2);
とかやると、0から2までの数字が見事に表示されます。つまり、呼び出し先の関数の中でも通用していて、しかしながらmapの後に空になっています。
これだけだと当たり前じゃん!って言われて終わりそうなのですが、、例えば下のようなコードを考えてみます。
#!/usr/bin/perl use strict; sub foo { $_++; print "in func foo($_)\n"; map { $_++; print " - innner map:$_\n"; } (0..2); } sub bar { $_++; print "in func bar bar($_)\n"; } map { print "\n\$_=[$_]\n"; foo; bar;} (0..2); print "now \$_=[$_]\n";
fooとbarという二つの関数があって、中でそれぞれ$_をインクリメントしています。また、fooはインクリメントするだけでなく、さらにその中でmapを使って0から2までの数字を表示しています。これを実行すると以下のような出力になります。
$_=[0] in func foo(1) - innner map:1 - innner map:2 - innner map:3 in func bar bar(2) $_=[1] in func foo(2) - innner map:2 - innner map:3 - innner map:4 in func bar bar(3) $_=[2] in func foo(3) - innner map:3 - innner map:4 - innner map:5 in func bar bar(4) now $_=[]
なんとfooの中のmapで出力される値が毎回変わっているんです!fooとbarの中の$_が同じものをさしているのは理解できるのですが、mapの初期値がかわるのはなんとも不思議です。fooの先頭でインクリメントされた$_の値がそのまま引き継がれているという解釈もできますが、それだとbarの中で値が戻っている(foo内のmapでのインクリメントが反映されていない)という矛盾が生まれます。
なんでなんだろうと悶々としていたら、「プログラミングPerl」の880ページ目に以下の記述が。。
map
map BLOCK LIST
map EXPR, LIST(省略)
LISTが名前を持った配列で場合に奇妙な結果がもたらすことがある。このような処理には、通常のforeachループを使うほうがわかりやすいだろう。
実際にfoo内のmap部分をforeachで書き直してみると、
$_=[0] in func foo(1) - innner map:1 - innner map:2 - innner map:3 in func bar bar(2) $_=[1] in func foo(2) - innner map:1 - innner map:2 - innner map:3 in func bar bar(3) $_=[2] in func foo(3) - innner map:1 - innner map:2 - innner map:3 in func bar bar(4) now $_=[]
と、想像通りの出力になりました。。
うーん。。perlもまだまだ知らない事だらけだ。。
id:boxphere先生、id:kazeburo先生、アドバイス本当にありがとうございました。
今日はid記法をマスター。申し訳ありませんでした。