続・map と grepの不思議

id:bsheep さんから先日の疑問について言及をいただきました。ありがとうございます。(^_^)
黒羊がだらだらとPerlとLinuxを勉強してる記録 - grepの不思議?

つまり、

    use strict;
    $_ = "abc";
    my @out = grep /c$/, qw(abc vds bca sfc skc);
    print "$_\n" for @out;

grepの第一引数はブロックでもいいわけで、つまり

    use strict;
    $_ = "abc";
    my @out = grep {/c$/} qw(abc vds bca sfc skc);
    print "$_\n" for @out;

と等価なわけで

実はこの2つの書き方が「等価である」理由がよくわからなかったんです。「式を渡せる」って変な感じではありませんか? 「ラムダ」や「クロージャ」「ブロック」「サブルーチン」などを渡すのならわかるんですが、「式」を直書きで渡せる言語は珍しいように思います。


昨日勉強した関数プロトタイプを使うと、後者の「ブロックを受け取る」書き方は再現できました。コードブロックは「&」で、リファレンスとして受け取れます。

use strict;
 
sub my_grep(&@) {
  my $block_ref = shift;
  my @ret = ();
  for (@_) {
    if ( $block_ref->() ) {
      push @ret, $_;
    }
  }
  return @ret;
}
 
my @evens = my_grep { $_ % 2 == 0 } (1..10);
print "@evens";

実行結果は以下の通りです。

2 4 6 8 10

1?10 の中の 2 で割り切れるものだけを抽出しています。
でも、この my_grep の呼び出しを以下のように変えると……

my @evens = my_grep $_ % 2 == 0, (1..10);

以下のようなエラーが出てしまいます。

Type of arg 1 to main::my_grep must be block or sub {} (not numeric eq (==)) at
D:\dev\perl\proto4.pl line 14, near ");"
Execution of D:\dev\perl\proto4.pl aborted due to compilation errors.

曰く「my_grep の第一引数はブロックか sub {} じゃないとダメ」との事です。
ちなみに、my_grepgrep に置き換えると……

my @evens = grep $_ % 2 == 0, (1..10);
2 4 6 8 10

ちゃんと動きます。(^_^;)
grep に「式」を渡せるのは、やはり組み込み関数だから特別扱いされているのかも。

こうすると明らかに$_ = "abc";がgrepの中で効果を発揮していないことが分かります。
って、つまり$_はmyを付けないからグローバルに見えるけど、実はちゃんとそれぞれレキシカルですよ。ってところが誤解の理由なんでしょうかね?

なるほどなるほど。確かに、スコープなども絡んできて複雑な問題ですね。grep の第一引数が違うスコープで評価されているのはわかるのです。そうじゃないと、grep の実行が終わった後に値が戻る理由がつきませんよね。

$_ = "hoge";
my @evens = grep { $_ % 2 == 0 } (1..10);
print "$_";
hoge

確かに、$_ は grep の外と中では違う変数のように振舞っています。値の変更を局所化するダイナミックスコープというべきなのかな?
このように書いた場合

my @evens = grep { $_ % 2 == 0 } (1..10);

「{ }」で囲まれているので、新しいスコープが作られている事は一目瞭然ですよね。
ところが、「{ }」で囲んでいるわけでもなく、単に

my @evens = grep $_ % 2 == 0, (1..10);

と書いただけなのに、「新しいスコープ」になるのがやはり不思議です。


もしかすると、僕は「人にとって当たり前の事」を「なんでだろう?なんでだろう?」と言っているのかもしれません。考えを伝えるのはとっても難しいと感じる瞬間です。(^_^;)
でも、こういったやりとりも、とても勉強になります。ありがとうございます♪ > bsheep さん