bonar note

京都のエンジニア bonar の技術的なことや技術的でない日常のブログです。

名前のない配列の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記法をマスター。申し訳ありませんでした。