Function::Parameters の紹介をしてみたいと思います
これは、Perl v5.14 以上に、次のような関数定義方法を提供してくれます1
use Function::Parameters;
use Types::Standard qw/Str/;
fun hello(Str $world) {
print "Hello $world"
}
型制約
上述の例の通り引数に型制約が指定できます
性能は、次のBenchmarkで試した限り、Data::Validator, Kavorka よりも性能は良いようです2
# perl: 5.026001
# validation modules:
# Data::Validator/1.07
# Function::Parameters/2.001003
# Kavorka/0.037
# type system: Types::Standard/1.002001
# with type constraints
# Rate D::Validator Kavorka F::Parameters
# D::Validator 172031/s -- -20% -62%
# Kavorka 214369/s 25% -- -53%
# F::Parameters 455903/s 165% 113% --
指定できる型制約は、$type_constraint->check($value)
, $type_constraint->get_message($value)
が duck type されているなら良く、Mouse, Moo, Type::Tiny などの型制約と相互運用できます3
クイックガイド
関数定義方法をまとめます
デフォルト値は、$x=xxx
名前付き引数を使う場合は、:$x
メソッド定義をしたい場合は、method
を利用して定義することが特徴だと思います
code ............................... | 説明 | 呼び出し例 |
---|---|---|
fun foo( ) |
引数なし |
foo , foo()
|
fun foo($x) |
引数1つ | foo(1) |
fun foo($x=123) |
引数1つ、default 0 |
foo(1) , foo() , |
fun foo($x, $y) |
引数2つ | foo(1, 2) |
fun foo(@args) |
引数複数 | foo(1, 2, 3) |
fun foo(%args) |
引数複数、ただし、偶数個 | foo(x => 1, y => 2) |
fun foo($x, $y, @args) |
引数2つ以上 | foo(1, 2, 3) |
fun foo($x, $y, %args) |
引数2つ以上、ただし、最後の引数は偶数 | foo(1, 2, z => 789) |
fun foo(:$x) |
名前付き引数1つ | foo(x => 1) |
fun foo(:$x, :$y) |
名前付き引数2つ | foo(x => 1, y => 2) |
fun foo(:$x, :$y, @args) |
名前付き引数2つとその他 | foo(x => 1, y => 2, 3) |
fun foo(:$x, :$y, %args) |
名前付き引数2つ、ただし、最後の引数は偶数 | foo(x => 1, y => 2, z => 3, w => 4) |
method foo(...) |
Object method | $self->foo() |
method foo($class: ... ) |
Classs method | Foo->foo() |
その他、次のようなトピックは、公式のドキュメントをみてもらうのが良いように思います
実際どう動いているの?
もう少し挙動の中身を追うため、Deparse をしてみようと思います4
わかりやすさのため、Types::Standard
ではなく、Dummy の型制約定義をして、Deparse をかけてみます
package DummyType {
sub new { bless {}, $_[0] }
sub check { 1 }
sub get_message { 'NG!!' }
}
package main;
use Function::Parameters;
fun Str() { DummyType->new() }
fun hello(Str $world) {
print "Hello $world"
}
% perl -MO=Deparse,-d hello2.pl
package DummyType;
sub new {
bless {}, $_[0];
}
sub check {
1;
}
sub get_message {
"NG!!";
}
package main;
{;};
use Function::Parameters;
sub Str {
BEGIN {
$^H{'Function::Parameters/config'} = 'HASH(0x7fd4ae15e048)';
}
Function::Parameters::_croak("Too many arguments for fun Str (expected 0, got " . @_ . ")") if @_ > 0;
"DummyType"->new;
}
sub hello {
BEGIN {
$^H{'Function::Parameters/config'} = 'HASH(0x7fd4ae15e048)';
}
Function::Parameters::_croak("Too few arguments for fun hello (expected 1, got " . @_ . ")") if @_ < 1;
Function::Parameters::_croak("Too many arguments for fun hello (expected 1, got " . @_ . ")") if @_ > 1;
my($world) = @_;
Function::Parameters::_croak("In fun hello: parameter 1 (\$world): " . bless( {}, 'DummyType' )->get_message($world)) unless bless( {}, 'DummyType' )->check($world);
print "Hello $world";
}
BEGIN {
$^H{'Function::Parameters/config'} = 'HASH(0x7fd4ae15e048)';
}
;
;
;
要するに、
hello の内部で、引数の数の確認と型の確認をし、合わなければ、croak するようなコードです
hello2.pl の挙動を、例えば、default 値をとるように次のようにしたとすると、
hello(Str $world='foo') ..
Deparse するとこんな具合です
my($world) = @_;
$world = "foo" if @_ < 1;
結構シンプルなのかなって思います!
まとめ
- Function::Paramerters は、v5.14 以降に関数定義手法のsyntaxと型制約を提供してくれる
- 性能は、よさそう
- 挙動は、subroutine 内部で、引数、型チェックするようなコードの生成しているだけ5
以上、Function::Parameters の紹介でした〜
-
いわゆる subroutine signatures のこと ↩
-
型制約の性能に関して、Params::Validate,Smart::Args は、Data::Validator 同封の benchmark で、Data::Validator 未満という結果でした。Method::Signatures は、Any::Moose 依存の解消がされていないため、候補から外しました ↩
-
https://metacpan.org/pod/Function::Parameters#Type-constraints ↩
-
挙動のカスタマイズは諸々できる https://metacpan.org/pod/Function::Parameters#Customizing-and-extending ↩