これは 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 で○×ゲームをガチバトルする話みたいです。めっちゃレベル高いな。