Perl 5.19.9 で実装された signatures の構文をためしてみる - tokuhirom blog を見てそんなのできたのか〜と思ったので、いろいろ試してみた。なんとこれは、今までになく直感的に引数が書けてしまう革命的構文です。
use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";
use feature 'lexical_subs';
no warnings "experimental::lexical_subs";
# 引数の数チェックをしてくれる。便利
eval {
say "check length of arguments";
state sub hello ($foo) {
say "hello $foo";
}
hello();
#=> Too few arguments for subroutine at pl.pl line 9.
}; if ($@) { warn $@ }
# デフォルト値も普通に書けます
eval {
say "default value";
state sub hello ($foo="world") {
say "hello $foo";
}
hello();
#=> "hello world"
hello("yunotti");
#=> "hello yunotti"
}; if ($@) { warn $@ }
# デフォルト値に式も書けます。Ruby なんかでも書けますね。引数として指定されない場合だけ氷菓されます
eval {
say "expression in default value (evaluated same as Ruby)";
my $i = 0;
state sub hello ($foo=$i++) {
say "hello $foo";
}
hello();
#=> hello 0
hello();
#=> hello 1
hello("yunotti");
#=> "hello yunotti"
hello();
#=> hello 2
}; if ($@) { warn $@ }
# @foo を指定したら可変引数にできます。%hash とかもできます。
eval {
say "variable length arguments";
state sub hello ($foo, @rest) {
say "hello $foo and " . join(", ", @rest);
}
hello("yunotti", "miyachan", "sae", "hiro");
#=> hello yunotti and miyachan, sae, hiro
}; if ($@) { warn $@ }
# @_ の挙動は今までどおり変数渡しです。
eval {
say 'with @_: @_ is passed by variable (same as prev perls)';
state sub hello ($foo, $bar) {
$foo = 'xxx';
$_[1] = 'xxx';
}
my $foo = 'foo';
my $bar = 'bar';
hello($foo, $bar);
say "$foo, $bar"; #=> foo, xxx
}; if ($@) { warn $@ }
ただ引数の名前とかは外からとることができない。せっかく構文に組込まれたのなら、とれてもよさそうだなと思った。すこしコード追ってみたけど、基本、今までで同じようなコードが内部的に生成されるだけっぽい。サブルーチンリファレンスに附属する形でなんかメタデータを入れれたらいいんだけど、よくわからなかった。
あと、Smart::Args みたいなのは使えるかと思って試してみたけど、既存コードと同じ感じなので、当然普通に使える。ただ、signatures を活用した感じにはできなそう。うまいやりかたあるのかな。
use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";
use feature 'lexical_subs';
no warnings "experimental::lexical_subs";
{
# Smart::Args は普通に使える
use Smart::Args;
{
# % を書けば呼ばれる時点でハッシュかどうかのチェックは入る
state sub hello (%) {
args(my $foo);
say "hello $foo";
}
hello(foo => "foobar");
eval {
hello(1);
}; if ($@) { warn $@ }; #=> Odd name/value argument for subroutine at pl.pl line 20.
};
{
# % を書かなければ今までどおり Smart::Args 側でエラる
state sub hello {
args(my $foo);
say "hello $foo";
}
eval {
hello(1);
}; if ($@) { warn $@ }; #=> Odd number of elements in anonymous hash at lib/site_perl/5.19.9/Smart/Args.pm line 39.
}
};
{
# せっかくなら signatures を活用したい感あるけどいい感じにならない (引数名を2度書かないといけない)
use PadWalker qw/var_name/;
sub validate ($var, $rule) {
my $name = var_name(1, \$_[0]) =~ s{^\$}{}r;
require Smart::Args;
$_[0] = Smart::Args::_validate_by_rule($_[0], 1, $name, $_[1]);
}
{
state sub hello ($foo) {
validate($foo, { isa => 'Int' });
say "hello $foo";
}
eval {
hello("xxx");
}; if ($@) { warn $@ }; #=> Validation failed for 'Int' with value xxx
}
};