第140回 ブロックを渡せるのはRubyだけじゃない! Perlだって渡せるんだ!

YAPCが始まりましたね。
去年は参加したのですが(聴衆の方ですが)、今年は非常にとてつもなく残念な事に参加できません。


でも気分だけでもひたりたいのでPerlのお話を少々。

Rubyに備わったブロックという概念

Rubyは言語自体に高階関数をブロックという形でもっており、
eachなどで使われているのは有名です。


例:

array = [1,3,5,9]
array.each{|i| puts i}

eachに与えている { }の部分がブロックです。
自前でブロックを受け取る関数を作りたければ yield を使えばよいでしょう。


今回のお話の内容は「Perlでもブロックを渡せる」ということなので、
eachの機能, yieldの機能は知っておられるという前提で先にすすみたいと思います。


Perlのブロックを使った例

それではRubyの話はこれくらいにしてPerlの話にまいりましょう。
まずPerlでこの高階関数のように振る舞うブロックを使った組込関数を見ていきたいと思います。
今回はmapを使った例を紹介します。

use Data::Dumper;

my @c = 1..5;
my @d = map { $_ * 2 } @c;  # @cの各要素を2倍した配列を返す

print Dumper \@d;

結果:

$VAR1 = [
          2,
          4,
          6,
          8,
          10
        ];

ソース中のコメントにも書きましたが 各要素を2倍した配列を返しています。

map ブロック 配列

という書式です。


ブロックを引数にとる関数の作り方

このようにブロックを引数にとる関数は自前でつくることができます。
このことにより、あたかもPerlにもとから備わっている組込関数もしくは文法のように振る舞わせることが(書くことが)可能となります。


定義の仕方は簡単。以下のようにプロトタイプを使えばよいだけです。

sub(&){  }

このようにPerlのプロトタイプで&を指定することでブロックを受け取ることができます。

sub hoge(&){
 $code = shift;
 $code->();
}

hoge { print 'Hello World' . "\n" }   # ブロックを渡す

これだけだと、だから何?という感じですので、
実用的な例を紹介しましょう。
Perlの5.8から組み込まれたList::Utilに備わったfirstを使うと

use List::Util qw(first);

my @c = 1..5;
my $d = first { $_ > 3 } @c;  #最初に3を超えるものを取得
print $d . "\n";

このように最初に3を超えるものを取得することができます。


これを自作するならば、

sub first(&@){
  my $code = shift;
  for(@_){
    return $_ if $code->();
  }
}

my @c = 1..5;
my $d = first { $_ > 3 } @c;
print $d . "\n";

このようになります。
実際、バージョン1.21のList::Util::PP.pmの44行目にほとんど同じソースを発見できました。

try{ }catch{ }構文だって作れる

このブロックを使えば、try{ }catch{ }構文だって作れます。
これをやったのがError.pmです。どのようにやってのけているのかは

プログラミングPerl〈VOLUME1〉

プログラミングPerl〈VOLUME1〉

に少し説明されていました。


概略だけ簡単に説明すると、

sub try(&$){  }
と
sub catch(&){  }

を使い、実にうまい具合に tryのブロックが先に処理されるように実装されているようです。


DSLだって作れる

ブロックを渡せることによって最近流行のDSLだって作る事ができます。
これをやっているのが、Web::Scraperです。

2007-05-09

scraper {

}

のブロックの中にどのような処理を行なってほしいのか示すことができます。

ブロックを受け取れるようにできるのは第1引数だけ

最後に注意事項です。
プロタイプに&を書いてブロックをもらうことができるのは、
ブロックを第1引数にもらうときだけとなります。





参考:
http://perl-mongers.org/2009/01/re_map.html
第81回 高階関数を理解できないとRubyは理解できない - bingo_nakanishiの他言語出身者のためのPerl入門