Function::Parameters の紹介

[Function::Parameters](https://metacpan.org/pod/Function::Parameters) の紹介をしてみたいと思います
これは、Perl v5.14 以上に、次のような関数定義方法を提供してくれます[^1]

```perl:hello.pl
use Function::Parameters;
use Types::Standard qw/Str/;

fun hello(Str $world) {
  print "Hello $world"
}
```

# 型制約

上述の例の通り引数に型制約が指定できます
性能は、次の[Benchmark](https://gist.github.com/kfly8/baa26c59b0a0685427e08cdd92a55d72)で試した限り、[Data::Validator](https://metacpan.org/pod/Data::Validator), [Kavorka](https://metacpan.org/pod/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()` |

その他、次のようなトピックは、公式のドキュメントをみてもらうのが良いように思います

- [デフォルト値のより込み入った使い方](https://metacpan.org/pod/Function::Parameters#Default-arguments)
- [Method modifiers](https://metacpan.org/pod/Function::Parameters#Method-modifiers)
- [prototypes and attributes](https://metacpan.org/pod/Function::Parameters#Prototypes-and-attributes)
- [Function::Parameters のカスタマイズ](https://metacpan.org/pod/Function::Parameters#Customizing-and-extending)

# 実際どう動いているの？

もう少し挙動の中身を追うため、Deparse をしてみようと思います[^4]
わかりやすさのため、`Types::Standard` ではなく、Dummy の型制約定義をして、Deparse をかけてみます

```perl:hello2.pl
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
% 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 値をとるように次のようにしたとすると、

```perl
hello(Str $world='foo') ..
```

Deparse するとこんな具合です

```perl
    my($world) = @_;
    $world = "foo" if @_ < 1;
```

結構シンプルなのかなって思います!

# まとめ

- Function::Paramerters は、v5.14 以降に関数定義手法のsyntaxと型制約を提供してくれる
- 性能は、よさそう
- 挙動は、subroutine 内部で、引数、型チェックするようなコードの生成しているだけ[^5]


以上、Function::Parameters の紹介でした〜



[^1]: いわゆる [subroutine signatures](https://perldoc.perl.org/perlsub.html#Signatures) のこと
[^2]: 型制約の性能に関して、[Params::Validate](https://metacpan.org/pod/Params::Validate),[Smart::Args](https://metacpan.org/pod/Smart::Args) は、Data::Validator 同封の benchmark で、Data::Validator 未満という[結果](https://gist.github.com/kfly8/baa26c59b0a0685427e08cdd92a55d72#gistcomment-2279759)でした。[Method::Signatures](https://metacpan.org/pod/Method::Signatures) は、[Any::Moose 依存の解消](https://rt.cpan.org/Public/Bug/Display.html?id=88716)がされていないため、候補から外しました
[^3]: https://metacpan.org/pod/Function::Parameters#Type-constraints
[^4]: Deparse するのに、-d をいれて、ワークアラウンドしています [参照](https://github.com/mauke/Function-Parameters/issues/29#issuecomment-294137390)
[^5]: 挙動のカスタマイズは諸々できる https://metacpan.org/pod/Function::Parameters#Customizing-and-extending