関数に型制約を導入する
関数の引数と戻り値に対し型制約を適用することで、
Function::Parameters──関数の引数の型制約
Type::Tiny
にはType::Params
という引数チェック用ライブラリが同梱されていますが、Function::Parameters
2.Function::Parameters
はパフォーマンスこそ若干Type::Params
に劣るものの
Function::Parametersの使い方
Function::Parameters
は、fun
もしくはmethod
キーワードで関数定義を行います。関数の引数にStr $msg
と書けば、$msg
がStr
の型制約を満たすかを判定します。指定する型制約は、check
メソッドと、get_
メソッドさえダックタイピングされていればよく、Type::Tiny
はもちろん、Moose
も利用できます。
use Function::Parameters;
use Types::Standard -types;
hello('world!'); # => HELLO world!
fun hello(Str $msg) {
print "HELLO $msg";
}
名前付き引数は:$name
のように変数名にコロンを付け、$name=DEFAULT
のように指定できます。
foo(name => 'foo!'); # => foo!
fun foo(Str :$name) {
print $name;
}
bar(); # => bar!
fun bar(Str $name='bar!') {
print $name
}
Function::Parametersでコード検査を行う
関数の引数が多すぎると誤用が起きることは容易に想像できます。Function::Parameters
では定義した関数から引数の数を含むメタ情報を抽出できるので、
package Foo;
use Function::Parameters;
fun aaa($a, $b) { }
fun bbb($a, $b, $c, $d, $e) { }
package test;
use Test::More;
use Module::Functions;
use Function::Parameters;
subtest 'prohibit too many args' => sub {
my $MAX = 4;
my $pkg = 'Foo';
my @functions = get_public_functions($pkg);
for my $f (@functions) {
my $name = "${pkg}::${f}";
my $code = \&{$name};
my $info = Function::Parameters::info $code;
ok $info->args_max <= $MAX, $name;
# =>
# not ok 1 - Foo::bbb
# ok 2 - Foo::aaa
}
};
done_testing;
引数の個数の検査以外にも、
Function::Return──関数の戻り値の型制約
拙作のFunction::Return
0.Str
かチェックしています。
use Function::Return;
use Types::Standard qw(Str);
sub hello :Return(Str) { $_[0] }
my $msg1 = hello('world');
my $msg2 = hello({}); # error!
Function::Parameters
と組み合わせて使うこともでき、
use Function::Parameters;
use Function::Return;
use Types::Standard -types;
use Test::More tests => 1;
fun Blood() {
Str & sub { $_ =~ qr!(?:A|B|O)(?:A|B|O)! }
}
# 交配したときの血液型の組み合わせ
fun mate(Blood $a, Blood $b) :Return(ArrayRef[Blood]) {
my @a = split //, $a;
my @b = split //, $b;
[ map {
my $a = $_;
map { join '', sort $a, $_ } @b } @a ];
}
is_deeply mate('AO', 'BO'), ['AB', 'AO', 'BO', 'OO'];
Function::Returnでメタ情報を取得する
Function::Parameters
と同様に、Function::Return
も戻り値に関するメタ情報を取得でき、$info->types
で指定した型制約を取り出せます。
use Function::Parameters;
use Function::Return;
use Types::Standard -types;
fun hello(Str $msg) :Return(Str) {
return "HELLO $msg"
}
my $pinfo = Function::Parameters::info \&hello;
my $rinfo = Function::Return::info \&hello;
$rinfo->types; # [Str]
関数呼び出しのコンテキストの固定
Perlは関数の呼び出し方に応じて、wantarray
の値に応じ、
しかし、Function::Return
は、
use Function::Return;
use Types::Standard -types;
sub multi :Return(Num, Str) { 3.14, 'hello' }
my ($pi, $msg) = multi();
my $count = multi(); # ERROR! Required list context.
Perlに慣れている人からすると逆に不自然かもしれませんが、Num
, Str
で返却する約束を優先するようになっています。
Function::Returnのオプション設定
Function::Return
はReturn
という関数の属性をエクスポートしますが、Out
という名前に変更しています。
use Function::Return name => 'Out';
use Types::Standard -types;
sub foo :Out(Int) { 1.2 }
foo() # => ERROR! 1.2 is not INT.
以下はパフォーマンスを優先したい場合のオプションです。
use Function::Return no_check => 1;
sub foo :Return(Int) { 3.14 }
foo(); # NO ERROR!
戻り値の型チェックは、Function::Return
のオプションにno_
を指定することで、
まとめ
本稿では、Perl::Critic
を用いた静的検査を紹介しました。次に、Type::Tiny
で型制約を導入する方法を示しました。そして最後に、Function::Parameters
とFunction::Return
で関数の入出力を動的に型チェックし、
Perlの柔軟さを活かして、
さて、
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT