続・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_grep を grep に置き換えると……
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 さん