map と grep の不思議

map と grep の第一引数には「式」もしくは「ブロック」が渡せる事を勉強した時に、以下のような疑問を持ちました。

ところで、『grep に「式」を渡す時、以下のように書ける』と書きましたが

use strict;
 
my @fruits = qw(pine banana apple lemon grape mango);
print "@fruits\n";
 
my @e_fruits = grep $_ =~ /e$/, @fruits;
print "@e_fruits\n";

この書き方だと一見「$_ =~ /e$/」の評価結果が grep に引数として渡されてしまうように見えます。でも実際は正常に動きます。とても不思議です。
grep の場合は特別に遅延評価するようになっているのでしょうか? それとも自動的にブロックに変換されるのかな? と、疑問を投げかけてみる。(^_^)

後から読んでみると、ちょっと抽象的で意味不明な質問でした(^_^;)
この疑問は、例えばこのように書いた時がわかりやすいかも。

use strict;
 
$_ = "abc";
my @arr = grep /c$/, qw(acb bac bca cab cba);
print "@arr";

grep する前に $_ に "abc" を入れてあります。普通に考えれば grep に渡している /c$/ は grep が実行される前に評価されて($_ =~ /c$/)、真(1)を返すのではないでしょうか。そして、その返り値が第一引数として渡されるように見えます。

$_ = "abc";
my @arr = grep /c$/, qw(acb bac bca cab cba);
  ↓
my @arr = grep 1, qw(acb bac bca cab cba);

もし、このように解釈された場合は grep に渡した第二引数のリストがそっくりそのまま返ってくるはずです。なぜなら、第一引数は常に真だからです。
ところが実行してみると

bac

とだけ表示されます。つまり、「1」ではなく、ちゃんと正規表現の「式」を第一引数として渡せている事になりますね。(第二引数のリストの中で正規表現にマッチするのは bac だけ) これは map でも同じように動きました。
でも、なぜ“式”を“式として”渡せるのでしょうか。なぜ“引数が評価されずに”grep・mapが呼び出されるのでしょう。とっても不思議。


そこに、通りすがりの方がコメントでこの疑問について答えてくれました。以下に、通りすがりさんのコメントを引用します。
通りすがりさんのコメント(1)

# 通りすがり 『> grep の場合は特別に遅延評価するようになっているのでしょうか?
私もわからなかったので、他の場所で訊いてみたところ、「プロトタイプ(&@)と同じで、式でもブロックでも、コードリファレンスとして渡される」との事でした。ご参考までに。』 (2006/06/07 23:13)

どうやら秘密は「プロトタイプ」にあるようです。まだプロトタイプは勉強していないので詳しくは知りませんが、他の言語でプロトタイプというと「関数の仮定義」の事ですね。引数の数、それらの型、返り値の有無、その型などをあらかじめコンパイラに教えることができます。
恐らく Perl にも同じ機構があるのでしょうか。あらかじめ引数の型を教えておく事で、式を自動的にコードリファレンスに変換するような事ができるのかな? 数値⇔文字列の変換だって自動でやってくれるし、その位やってくれそう! 便利だなぁ!
などと考えていたら……
通りすがりさんのコメント(2)

# 通りすがり 『訂正です。プロトタイプ(&@)でも、式やブロックをそのまま渡す事はできないそうです。sub {} でコードリファレンスを渡す必要があります。』 (2006/06/08 00:36)

ありゃ(^_^;)
どうやらプロトタイプだけでは map や grep の動作は再現出来ないようですね。sub { } という構文は「無名関数を生成して、そのリファレンスを得る構文」でした。まだ何か謎が隠されていそうです。
とても勉強になりました。通りすがりさん、コメントありがとうございました!


というわけで、今後の予定として、とりあえずプロトタイプを勉強して、それから map や grep を研究してみます。
引き続き、疑問の答えも募集中です♪ よろしくお願いします。m(_ _)m