この記事は, 「Perl Advent Calendar 2017」の24日目の記事です.
昨日は, id:papix の「VimにおけるPerl関連のスニペットを晒してみる 〜2017年版〜」でした.
Smart::Argsは便利
Perlで, 関数に渡ってきた引数のチェック(バリデーション)をするのであれば, Smart::Argsを使うのが一般的だと思います.
Smart::Argsを使えば, 次のように関数に渡ってくる引数をチェックできます.
ここでは, func
という関数に対して, foo
というキーでInt
(数値), bar
というキーでStr
(文字列)が渡ってくることを指定しています.
use strict; use warnings; use utf8; use Smart::Args qw/ args /; func(foo => 1, bar => 'bar'); func(foo => 'foo', bar => 'bar'); sub func { args my $foo => 'Int', my $bar => 'Str'; }
最初の呼び出しは, foo
に1
という数値, bar
に'bar'
という文字列がそれぞれ渡っているので大丈夫ですが, 2回目の呼び出しは, foo
に'foo'
という文字列が渡っています.
2回目のfunc
を呼び出すと, Perlは次のようなエラーを返します:
'foo': Validation failed for 'Int' with value foo at /path/to/lib/Smart/Args.pm line 179. Smart::Args::_validate_by_rule("foo", 1, "foo", "Int", SCALAR(0x7fccba8e8df8)) called at /path/to/lib/Smart/Args.pm line 63 Smart::Args::args(undef, "Int", undef, "Str") called at smart_args.pl line 11 main::func("foo", "foo", "bar", "bar") called at smart_args.pl line 8
Smart::Argsを使えば, 「関数に渡ってきた引数のチェック」と, 「変数の宣言」が同時に出来るので, とても便利です.
Smart::Args::TypeTiny
そんな中, 最近Smart::Args::TypeTinyというモジュールが公開されました. 作者は id:akiym さん.
名前からわかる通り, 型にTypeTinyを使った*1Smart::Argsライクなバリデーターなのですが, Smart::Argsとの「IMCOMPATIBLE CHANGES」が, かなり痒い所に手が届くようになっていて, 個人的に注目しています.
期待していないパラメータが渡されるとエラーになる
例えば, 次のコードのように, func
関数はfoo
というキーでInt
が渡ってくることを期待しているとします.
use strict; use warnings; use utf8; use Smart::Args qw/ args /; func(foo => 1, bar => 'bar'); sub func { args my $foo => 'Int'; }
ここで, Smart::Argsの場合, func
にfoo
に加えてbar
というキーで値を渡した場合, 次のようにエラーではなく警告が発生します.
unknown arguments: bar at smart_args.pl line 10. main::func("foo", 1, "bar", "bar") called at smart_args.pl line 7
一方, Smart::Args::TypeTinyであれば, 出力される文言は同じですが, 警告ではなくエラーになります.
use strict; use warnings; use utf8; use Smart::Args::TypeTiny qw/ args /; func(foo => 1, bar => 'bar'); sub func { args my $foo => 'Int'; }
optional
はundef
が渡ってくることも許容する
Smart::Argsでは, 次のようにして, 引数をoptional(任意で渡すことが出来る)にすることが出来ます.
use strict; use warnings; use utf8; use Smart::Args qw/ args /; func(); # (1) func(foo => 1); # (2) func(foo => undef); # (3) sub func { args my $foo => { isa => 'Int', optional => 1 }; }
このとき, func
を呼び出す時にfoo
をキーとして値を渡されなければ, func
における$foo
はundef
になります(コード中の, (1)
のパターン).
もちろん, (2)
のように, foo
をキーとして値を渡せばfunc
における$foo
は渡された値(この場合は1
)になりますし, ここでInt
ではない値を渡せばエラーになります.
問題は, (3)
のようにoptionalな引数にundef
を渡した場合で, この時Smart::Argsではエラーが発生します.
'foo': Validation failed for 'Int' with value undef at /path/to/lib/Smart/Args.pm line 179. Smart::Args::_validate_by_rule(undef, 1, "foo", HASH(0x7fe8cf89f510), SCALAR(0x7fe8cf80e7f8)) called at /path/to/lib/Smart/Args.pm line 63 Smart::Args::args(undef, HASH(0x7fe8cf89f510)) called at smart_args.pl line 12 main::func("foo", undef) called at smart_args.pl line 9
一方, Smart::Args::TypeTinyであれば, (3)
のようにoptionalな引数にundef
を渡してもエラーにならず, func
における$foo
の値はundef
として処理を続けることができます.
default
にサブルーチンリファレンスを渡すことで, 必要な時だけ評価することができる
Smart::ArgsもSmart::Args::TypeTinyも, 次のようにdefault
を指定することで, その値が渡ってこなかった時のデフォルト値を指定することができます.
そしてこのとき, 何かしらのサブルーチンを呼び出してデフォルト値を埋めることが出来ます.
use strict; use warnings; use utf8; use Smart::Args qw/ args /; func(); # (1) func(foo => 1); # (2) sub func { args my $foo => { isa => 'Int', default => create_value() }; print "$foo\n"; } sub create_value { print "called!\n"; return 123; }
上のコードでは, func
を呼び出す時にfoo
というキーで値を渡さなかった場合, create_value
という関数を実行して, その結果をデフォルトの値にする... という挙動になります.
このコードを実行すると, Smart::ArgsもSmart::Args::TypeTinyも, 次のような結果になります.
called! 123 called! 1
foo
というキーを指定せずにfunc
を呼び出した場合, つまり(1)
の場合はもちろんのこと, foo
というキー指定した(2)
の場合でも, create_value
の呼び出しが発生してしまっています.
万が一, create_value
が多少時間のかかる処理であった場合, create_value
の呼び出しが不要であっても毎回呼び出されてしまうので, パフォーマンスの面でも気になって来るかもしれません.
一方, Smart::Args::TypeTinyでは, default
に対してサブルーチンリファレンスを渡すことで, 「必要な時だけ」デフォルト値を生成することが可能です.
use strict; use warnings; use utf8; use Smart::Args::TypeTiny qw/ args /; func(); func(foo => 1); sub func { args my $foo => { isa => 'Int', default => sub { create_value() } }; print "$foo\n"; } sub create_value { print "called!\n"; return 123; }
このコードを実行すると,
called! 123 1
このような出力になります.
create_value
が, 1度しか呼び出されていないことがわかります.
まとめ
最近個人的に注目している, Smart::Args::TypeTinyの紹介をしました. Smart::Argsを使っていない人はもちろんのこと, 既に使っている方も, Smart::Args::TypeTinyに置き換えていく価値は結構ありそうでは? と思っています.
*1:Smart::Argsは型にMouseを使っている