読者です 読者をやめる 読者になる 読者になる

高階関数perl(higher order perl)

perl高階関数したメモです。

高階関数

高階関数は関数を引数にしたり、戻り値にする関数のことです。
Javaではそういうことは出来ませんが、perlは普通に出来ます。
(Java8ラムダは高階関数というのだろうか???)
スクリプト言語は大体出来るのかなぁと思います。

基本

testという関数を作成します。
この関数は引数として関数のリファレンスを取り、そのまま実行します。
この関数にprintするだけの無名関数を与えて実行してみます。

higher_order_perl.pl

#!/usr/local/bin/perl
use strict;
use warnings;

sub test {
	my $func = shift;
	$func->();
}

test(sub {print "hello"});

実行。

# perl higher_order_perl.pl
hello

print文が表示されました。

grepを自作する

perl組み込み関数のgrepをエミュレートしてみます。

grepはこんな感じで、引数として評価する式とリストをとります。

grep {$_ % 2} @list

grepをエミュレートするmy_grepという関数を作ってみました。

sub my_grep {
	my ($func, $list) = @_;
	my @result;
	for (@$list) {
		push @result, $_ if ($func->($_)); 
	}
	return @result;
}

簡単な説明。
引数を2つ受け取りますが、スカラー変数で受け取ります。
これはperlでは引数は@_にまとめられてしまうため、リストを渡す場合には参照渡しをする必要があるからです。

sub my_grep {
	my ($func, $list) = @_;
       :
       :
}

なので、$listはデリファレンスしてやる必要があります。

@$list

あとは、引数で受けたリストに対して、引数で受けた関数を実行して、
式の評価がtrueの場合は結果として登録し、falseの場合は登録しないようにするだけです。

my_grepの実行は、無名関数とリストのリファレンスを指定します。

my @nums = 1 .. 9;
my @odd = my_grep(sub {$_[0] % 2}, \@nums);

確認用コード。
my_grep.pl

#!/usr/local/bin/perl
use strict;
use warnings;

sub my_grep {
	my ($func, $list) = @_;
	my @result;
	for (@$list) {
		push @result, $_ if ($func->($_)); 
	}
	return @result;
}

my @nums = 1 .. 9;
my @odd = my_grep(sub {$_[0] % 2}, \@nums);

print "before: @nums \n";
print "after: @odd \n";

実行。

# perl my_grep.pl
before: 1 2 3 4 5 6 7 8 9
after: 1 3 5 7 9

ただし、呼び出しの方法が組み込みのgrepと違い、
無名関数と配列のリファレンスを渡しています。

my_grep(sub {$_[0] % 2}, \@nums);

下記の組み込みのgrepの様な呼び出しをしたい場合、
プロトタイプ宣言を使うと実現できます。

grep {$_ > 2} @list
prototypeによる引数の宣言

perlでは関数の引数にプロトタイプを宣言することで、引数の型を指定できます。
下記の様なプロトタイプ指定ができます。

  • @, % : リストコンテキストを強制
  • $ : スカラーコンテキストを強制
  • & : 無名サブルーチンを要求

プロトタイプ宣言したmy_grepがこちら。

sub my_grep (&@) {
	my $func = shift;
	my @result;
	for (@_) {
		push @result, $_ if ($func->($_)); 
	}
	return @result;
}

簡単な説明。
引数を2つ受け取りますが、プロトタイプで無名サブルーチンとリストを強制しています。

sub my_grep (&@) {
       :
}

引数で渡すリストは、リストコンテキストを強制しているので、
デリファレンスせずにそのまま使用できます。

for (@_) {
     :
}

プロトタイプ版のmy_grepは組み込みのperlと同じ様に呼び出せます。

my @nums = 1 .. 9;
my @odd = my_grep {$_[0] % 2} @nums;

確認用コード。
my_grep_prototype.pl

#!/usr/local/bin/perl
use strict;
use warnings;

sub my_grep (&@) {
	my $func = shift;
	my @result;
	for (@_) {
		push @result, $_ if ($func->($_)); 
	}
	return @result;
}

my @nums = 1 .. 9;
my @odd = my_grep {$_[0] % 2} @nums;

print "before: @nums \n";
print "after: @odd \n";

実行

# perl my_grep_prototype.pl
before: 1 2 3 4 5 6 7 8 9
after: 1 3 5 7 9


mapも同様に作れます。

my_map_prototype.pl

#!/usr/local/bin/perl
use strict;
use warnings;

sub my_map (&@) {
	my $func = shift;
	my @result;
	for (@_) {
		push @result, $func->($_);
	}
	return @result;
}

my @nums = 1 .. 9;
my @double = my_map {$_[0] * 2} @nums;

print "before: @nums \n";
print "after: @double \n";

実行

# perl my_map_prototype.pl
before: 1 2 3 4 5 6 7 8 9
after: 2 4 6 8 10 12 14 16 18

終わり。

github.com