grep(/^$array1[$_]$/, @array2);は使えない?

[上に] [前に] [次に]
くろっくぁ 1999/11/30(火) 00:25:54
適切なタイトルを付けられませんでした。すみません。
こういう使い方は滅多にしないのですが、
気になったので質問させてください。

@array1 = (1,2,3);
@array2 = (5,9,1);

for (0..$#array1) {
print "\$_ マッチ\n" if grep(/^$array1[$_]$/, @array2);
}

for $i (0..$#array1) {
print "\$i マッチ\n" if grep(/^$array1[$i]$/, @array2);
}

としたとき本来なら
$_ マッチ
$i マッチ
と出力されるはずだと思うのですが、
$i マッチ
と出力されるだけなのです。
なぜなのでしょうか・・・。
教えてください。よろしくお願いします。

B-Cus 1999/11/30(火) 02:04:35
 grep(/^$array1[$_]$/, @array2)
は、@array2 から要素を1つずつ取り出し、$_ に代入して
/^$array1[$_]$/ を評価するので、最初の $_ は破壊されます。

 grep(/^$array1[$_]$/, @array2)
は、
 grep { /^$array1[$_]$/ } @array2
と等価。それを踏まえた上で、デバッグのために print 文を加える。
  @array1 = (1,2,3);
  @array2 = (5,9,1);
  for (0 .. $#array1) {
      print "check \$array1[$_](=$array1[$_])\n";
      grep {
          print "\$_=$_ \$array1[\$_]=\$array1[$_]=$array1[$_]\n";
          /^$array1[$_]$/ ? $_ : ();
      } @array2;
  }
と書き換える。実行結果は以下の通り。
  check $array1[0](=1)
  $_=5 $array1[$_]=$array1[5]=
  $_=9 $array1[$_]=$array1[9]=
  $_=1 $array1[$_]=$array1[1]=2
  check $array1[1](=2)
  $_=5 $array1[$_]=$array1[5]=
  $_=9 $array1[$_]=$array1[9]=
  $_=1 $array1[$_]=$array1[1]=2
  check $array1[2](=3)
  $_=5 $array1[$_]=$array1[5]=
  $_=9 $array1[$_]=$array1[9]=
  $_=1 $array1[$_]=$array1[1]=2
$_ と $array1[$_] を比べるわけなので、マッチしない。

ふじ 1999/11/30(火) 02:06:46
> grep(/^$array1[$_]$/, @array2);
この grep 文では @array2 の要素が順番に $_ に代入されて、
それから 第一引数 ( /^$array1[$_]$/ つまり $_ =~ /^$array1[$_]$/)
が評価されます。
だから、
$array1[$_]
は、
($array1[5], $array1[9], $array1[1] ) == (undef, undef, 2)
となるので、それぞれ
undef と 5
undef と 9
2 と 1
が評価の対象になるので、マッチしない、と。

# デフォルトを使いすぎるのは危険ですよ(^^;

ふじ 1999/11/30(火) 02:08:52
あら、かぶっちゃった。
# いつもながら丁寧な回答ですねえ。感心します>B-Cusさん

くろっくぁ 1999/11/30(火) 02:12:26
[[解決]]
なるほど、よくわかりました。
B-Cusさん、ふじさん、親切に説明していただき
どうもありがとうございました。

ふじ 1999/11/30(火) 02:17:35
# B-Cus さんのサンプルの実行結果を見れば明らかなのですが...

>最初の $_ は破壊されます。
破壊される、と言っても for 文内で使われている $_ が上書きされちゃう
訳ではなくて、 grep 内でのローカルな(my か?) $_ に値が代入されて
評価に使われる(から grep を抜けたら元の値に戻る)、
ということですね。
# 念のため。

mm 1999/11/30(火) 15:11:42
>grep 内でのローカルな(my か?) $_ に値が代入されて
grep内の $_ は、ダイナミックに参照できるので、my ではなく local だと思います。

それはともかく、foreach のループ変数が局所化されることは、
プログラミングPerlにも明記されてるんですが、
grep については見つけられなかったので、ちょっと試してみました。

$_ = 1;
p('out');
foreach (2) { p('foreach') };
p('out');
grep (p('grep'), (3));
p('out');
while (<DATA>) { chomp; p('while'); last; }
p('out');
sub p { print "$_[0]: $_\n"; }
__END__
4
===============
out: 1
foreach: 2
out: 1
grep: 3
out: 1
while: 4
out: 4

この結果から grep は foreach と同じですが、while では局所化されないようです。

$_ が参照となる場合には、配列本体がスコープから外れても
GCできないようになるのを防ぐために、
このような局所化が行われるのだと解釈したのですが、
識者のみなさんのご意見はいかがでしょうか?

B-Cus 1999/12/01(水) 03:12:48
> 破壊される、と言っても
失礼しました。「破壊」という表現は不適当でした。

> GC
GC って、Garbage Collection ですよね?

> 配列本体がスコープから外れてもGCできないようになるのを防ぐために、
というより、foreach/grep/map は、
 - $_ をいじると元配列が書き変わるようにしたかった
 - そのために local(*_)=変数 で局所化した
ってことじゃないでしょうか。例えば、
 @a=(1,2,3);
 foreach (@a){ $_ *=2 }
は、
 @a=(1,2,3);
 $i=0;
 while ( $i<=$#a ){
  local(*_)=\$a[$i];
  $_ *= 2;
 } continue {
  $i++;
 }
であると。

つまり局所化は、GC のためでなく、リファレンス機能を持たせたかったが
ための副作用では、ということです。

# perl のソースを読む能力はないので、全て想像です。

mm 1999/12/01(水) 14:25:23
>GC って、Garbage Collection ですよね?
そうです…スペリングに自信がなかったもので(^^;

>つまり局所化は、GC のためでなく、リファレンス機能を持たせたかったが
>ための副作用では、ということです。
なるほど、確かに $_ は、局所化しておかないと、危険ですね。
GCうんぬんというレベルの問題ではありませんでした。ありがとうございます。

しかし、local(*_)=\$a[$i]; っていうのは、my 変数のリファレンスを
型グロブに代入するのと同様に、理解不能なコードだなぁ…
それと、foreachの中では、$_は局所化されますが、@_はそのままですね。
(while, local では、@_も局所化されるので、これらは等価ではない)。
どちらも、ソース的に小細工してるってことかな…?

mm 1999/12/02(木) 00:24:27
すいません、以下のようにすれば、@_ は局所化されないで済みました。
これで、foreach と等価になりそう…

 @_ = (0);
 @a=(1,2,3);
 $i=0;
 while ( $i<=$#a ){
  local($_);
  *_=\$a[$i]; # 実用perlプログラミング 3.3.1 選択的別名定義
  $_ *= 2;
  push @_, $_;
  print "(@_)\n";
 } continue {
  $i++;
 }
 print "@_\n";

[上に] [前に] [次に]