結論 map と grep の不思議

id:bsheep さんに返事の返事を頂きました♪
黒羊がだらだらとPerlとLinuxを勉強してる記録 - 教えて、perldoc先生

失礼ながらこれはとっても尊敬すべきことのように思えます。ぜひ見習いたいものです。

あまりオススメできません。(^_^;) この一連の不思議のように、気になり始めるとどこまでも調べたくなるので、全く勉強が進みません。一歩一歩学ぶつもりが、そこかしこで道草をしています。(笑)

そうすると、splitも気になってきますねー。

確かに! split も正規表現(のマッチング式)を直に渡しているようにみえます。つまり、「$_」へのマッチの結果が split に渡されそうですが、実際にはちゃんと正規表現を渡せています。不思議ですね。

もうこれはLarry Wall氏に聞くしかないのではないか思います(えー)。というか、ただ単に、Perlの実行系が同じコードにコンパイルしているから、というのが個人的見解です。

なるほど。Perl の中の事は Perl を作った人(Larry Wall氏)にしかわからない、という事ですね。(^_^)
しかし、わざわざ作者さんに訊くまでもなく、他にも調べる方法はありますよね。例えば、Perlソースが公開されているので、ソースを読んでみる、という奥の手(?)があります。
というわけで、無謀ですが Perl 5.8.8 のソースコードをちょっとだけ読んでみました。(^_^) 以下に調べた事を書いておきます。私的なメモだと思ってください。ちょっと難しくなってしまったうえに、偏見と憶測だらけですので、書いてある内容を鵜呑みにするのはオススメできません。念のため。(^_^;)


ダウンロードした stable.tar.gz を展開すると、perl-5.8.8 フォルダが作られました。grep コマンドを駆使して、なんとなくあたりをつけ、組み込み関数(組み込み演算子)の最適化に使うデータの中から、関数を定義していると思われる記述を opcode.pl の「__END__」以降に見つけました。
例えば split の定義はこうなっています。以下 opcode.pl 652 行目から引用。

split		split			ck_split	t@	S S S

1列目が関数名、2列目が関数の説明、3列目が最適化の為に使うチェックルーチンの名前、4列目がオプション、5列目が引数との事です。
注目すべきは5列目の引数ですね。「S S S」となっていますが、「S」はスカラーを表しています。split が取る引数は最大3つなので、確かにこの通りです。つまり、split に渡す正規表現は「スカラーとして」渡されている、と予想できます。でもこれじゃあまだ「不思議」の説明にはなりません。
次に3列目に書かれているチェックルーチンを追いかけていって op.c 6129行目から始まる Perl_ck_split を大まかに読んでみると、第一引数は「OP_PUSHRE」という内部型として受け取られているように読めました。恐らく「RE (正規表現) を引数配列に PUSH (追加) する為の OP (演算子) 」だとは思いますが……要するにこの内部型は「正規表現パターンを扱っている」という事です。「OP_PUSHRE」を grep してみましたが、他にこの型を引数として受け取っている関数は見当たりませんでした*1
という事は、「split は正規表現を直に受け取る唯一の関数である」という推測が立ちます。つまり、「split は組み込み関数の中でも例外」なのだと思います。


さてさて、次に map と grep もどうなっているか調べてみます。同じように opcode.pl の __END__ 以降に続けて定義されていました。以下 opcode.pl 670 行目から 674 行目の引用です。

grepstart	grep			ck_grep		dm@	C L
grepwhile	grep iterator		ck_null		dt|	

mapstart	map			ck_grep		dm@	C L
mapwhile	map iterator		ck_null		dt|

後ろに「start」や「while」が付いていますが、これしか見当たりませんでした。
これはあくまで予想ですが、grep や map は内部ではこの2つの関数を組み合わせた物として扱われているのかもしれませんね。start で初期化して、while でループを回す。
start の5列目の引数に注目です。どちらも「C L」となっています。opcode.pl 中のコメントによると「C」は「sub (CV)」を表すと書かれています。「L」はリストです。
「S」(スカラー)ではなく「C」となっている辺り、やはりコードブロックや式が特別扱いされているように思います。ちなみにリファレンスは「R」ですので、コードリファレンスを受け取っているわけではないものと思われます。
エディタの正規表現検索で

/^([^\t]+?\t+){4}[^\t]*C/m

を検索してみました(つまり引数に「C」を含む関数定義の検索)。ヒットしたのは、「sort」「grepstart」「mapstart」のみ。そういえば sort も第一引数にブロックを受け取れましたね。
つまり、これらの関数だけが引数として「C」を取るという事です。これもやはり例外と言うべきなのかも。


というわけで自分なりの結論です。完全にコードを把握したわけではないので、憶測や仮説の上での結論であるという事をご理解ください。大いに間違っている可能性も考えられます。(^_^;)

  • grep map sort はコードブロックを受け取る例外的な関数(恐らく完全互換の自作は不可)
  • 正規表現パターンをスカラーとして直接受け取る事ができるらしい
  • split はパターンを直接受け取る唯一の関数

以上です。


とりあえず、この「不思議」についてはこの辺で決着とします。結局どこまでも調べてしまいました。他の勉強が進められない……。やはりオススメできません。(^_^;) >bsheep さん

*1:厳密には scalar 関数に渡せるようですが、内部で暗黙的に配列に変換しているようです。警告がでる模様。