2015-12-13
サブルーチンリファレンスの話
これは Perl入学式Advent Calendar13日目の記事です。実際に投稿できたのは1週間後くらいになってしまいました。。。すいませんすいません。いろいろバタバタしておりまして、投稿が遅れに遅れました。。。
こんばんは。東京でサポーターをしております、@tsucchiです。
昨日の記事は @magnolia_k_さんのPerl入学式に…参加したことがありません!でした。参加したことのない方にも評価してもらえるのって、ありがたいですね!嬉しいですね!
さてさて、僕は 昨年の Perl入学式 Advent Calendarで、リファレンスの使い方とかの話という記事を書きました。その中で、
リファレンスの使い道は大きく分けて 3 つくらいだと僕は考えています。
複数の配列を関数やメソッドに渡す
複雑なデータ構造を作る
関数やメソッドで引数を更新する
と書きましたが、忘れてた。完全に忘れてた。。。サブルーチンリファレンス
のことを!
さてさて、Perl 入学式の第5回では、Mojolicious を使って Web アプリの作成を行いますが、ここで実は唐突にサブルーチンリファレンスが登場します。
get '/' => sub {
my $self = shift;
$self->render(template => 'index');
};
この sub
ってなんだ???説明してないじゃん!、と唐突に気づいたのでした。というわけで、その落穂拾いも兼ねて、軽くサブルーチンリファレンスの説明をしようかな、と思います。
で、この sub
って本当にどうなってるんでしょ? Mojolicious のコードを見てみましょう…と思ったら、これめっちゃマジックだな。。。知らなかった。
と、いうわけで、もうちょっと分かりやすい例で行きましょうか。
まずは、普通のサブルーチンです。
sub add {
my ($arg1, $arg2) = @_;
return $arg1 + $arg2;
}
引数を足し算するやつですね。こんな感じで使います。
print add(1, 2); # => 3
さてさて、この add()
に a
とか b
とか、ヘンな値を渡すとどうなるでしょうか?
print add('a', 'b') # => 0
この場合、値は0
になります。んで、警告がでます。Argument "b" isn't numeric in addition (+) ...
みたいな。
では、これをチェックしましょうか。
sub check_args {
my ($arg1, $arg2) = @_;
if ( $arg1 !~ /^[+-]?\d+$/ || $arg2 !~ /^[+-]?\d+$/ ) {
die "引数は整数以外ダメですー";
}
}
何を許容するかは実は結構難しいですが、とりあえず整数ならOKということにしてみました。こんな感じにすればいいのかな
my $arg1 = 'a';
my $arg2 = 'b';
check_args($arg1, $arg2); # => ここでエラーになる
add($arg1, $arg2);
チェックは add()
でやってもいいですね。こんな感じです。
sub add {
my ($arg1, $arg2) = @_;
check_args($arg1, $arg2);
return $arg1 + $arg2;
}
さてさて。「何を許容するかは実は結構難しい」と書きました。仮に整数としましたが、これって、要件に応じて「正の整数」だったり、「小数もOK」だったり、あんまりないかもしれないけど、「負の数だけOK」だったり、実は色々バリエーションがありえそうです。
と、すると、サブルーチンを呼んでいる外側からコントロールできると良さそうですね。
ここで活躍するのがサブルーチンリファレンスです。こんな感じで使います。
sub add {
my ($arg1, $arg2, $check_subref) = @_;
$check_subref->($arg1, $arg2);
return $arg1 + $arg2;
}
引数にサブルーチンリファレンスをとる感じにしてみました。配列@array
の時の要素1番目は$array[0]
でした。で、このリファレンス
my @array = (1,2,3);
my $array_ref = \@array_ref;
の場合に1番目の値をとる場合は $array_ref->[0]
となります。同じような感じで、サブルーチンリファレンスに渡ってきたやつを使って、渡ってきたやつを呼ぶ時は、$subref->()
みたいな感じで呼びます。
全体としてはこんな感じ。
sub add {
my ($arg1, $arg2, $check_subref) = @_;
$check_subref->($arg1, $arg2); #ここで渡ってきたリファレンスがサブルーチンとして呼ばれる
return $arg1 + $arg2;
}
sub check_args {
my ($arg1, $arg2) = @_;
if ( $arg1 !~ /^[+-]?\d+$/ || $arg2 !~ /^[+-]?\d+$/ ) {
die "引数は整数以外ダメですー";
}
}
my $arg1 = 'a';
my $arg2 = 'b';
my $subref = \&check_args; #これがサブルーチンのリファレンス
add($arg1, $arg2, $subref);
むずいですね。。。僕も結構ちゃんと理解するまで時間かかったんですよね。。。
逆に、Mojolicious の例みたいに、無名サブルーチンを使った方が分かりやすいかもです。
sub add {
my ($arg1, $arg2, $check_subref) = @_;
$check_subref->($arg1, $arg2); #ここで渡ってきたリファレンスがサブルーチンとして呼ばれる
return $arg1 + $arg2;
}
my $arg1 = 'a';
my $arg2 = 'b';
add($arg1, $arg2, sub {
my ($arg1, $arg2) = @_;
if ( $arg1 !~ /^[+-]?\d+$/ || $arg2 !~ /^[+-]?\d+$/ ) {
die "引数は整数以外ダメですー";
}
});
add
の第3引数は sub
で始まるブロックになっています。これは Mojolicious で使っているのと同じやつで、無名サブルーチンというやつです。やっていることは先ほどのコードと同じです。チェックのサブルーチンが色々な所で使い回すものだったら、普通に定義して、リファレンスをとる方が良いでしょう(無名にしない、2個前のやつ)。そうでなくて、1回きりの使い捨てであれば、いちいち名前をつけるのも面倒なので、無名で良いじゃん、というのが1個前のやつです。Mojolicious のやつは、文法的にはこれと同じなのだけど、考え方はちょっと違っていて、「DSL」ってキーワードで色々調べてみるとちょっと世界が広がるかも。。。
と、いうわけで、結構難しいサブルーチンリファレンスのお話でした。
明日(?)は、@tomchaさんのPerl で○×ゲームをガチバトルする話みたいです。めっちゃレベル高いな。