Perl Smart::Argsで引数チェック

# Perl 引数チェック

こんにちは！
皆さんPerl書いてますか？

今日は関数の引数のチェックについてのお話をしていきます。

とはいえ便利なモジュールがあるのでそのご紹介です。

## Smart::Args

https://metacpan.org/pod/Smart::Args

今回はこのモジュールを使って値をチェックしていきたいと思います。

### インストール

いつものcpanmを実行してpackageを入れて使えるようにしましょう。

`$ cpanm -v Smart::Args`

最初はいろいろ入るので長いですが気長に待ちましょう。。。

### 使い方

使い方は簡単。

主に下記の２つを使用します。

* args
    * 名前つきの引数をチェックしたいとき

* args_pos
    * 名前つきではないとき

パッと説明してもわからないと思いますので、実際のコードを見ていきたいと思います。

### args

名前つきの引数をチェックする

使い方は簡単で、引数を受け取るところに`args`を付けるだけ。
受け取る側の変数名 が 引数のキー として利用できます。

```perl
use Smart::Args;

&hoge{piyo => 1};

sub hoge {
    args my $piyo;
    print $piyo;
}
```

引数が複数あるときは、並べて書いていく。

```perl
use Smart::Args;

&hoge(piyo => 1, fuga => 2); # OK

sub hoge {
    args my $piyo, my $fuga;

    printf "piyo: %d, fuga: %d\n", $piyo, $fuga;
}
```

`piyo`と`fuga`が渡されているかチェックできます。
問題なければ正常終了します。

```text:結果出力
piyo: 1, fuga: 2
```

#### 引数が足りないとき

```perl
use Smart::Args;

&hoge(piyo => 1); # 引数が足りない(fuga)

sub hoge {
    args my $piyo, my $fuga;

    printf "piyo : %d, fuga; %d\n", $piyo, $fuga;
}
```

```shell:実行結果
missing mandatory parameter named '$fuga' at sample.pl line 6.
        main::hoge("piyo", 1) called at sample.pl line 3
```

エラーがでてますね。

ちなみに 引数が多いとき は正常終了します。

```perl:sample.pl
use Smart::Args;

&hoge(piyo => 1, fuga => 2, foo => 3); # 引数が多い(foo)

sub hoge {
    args my $piyo, my $fuga;

    printf "piyo: %d, fuga: %d\n", $piyo, $fuga;
}
```

```shell:実行結果
piyo: 1, fuga: 2
```

#### 型のチェック

```perl:sample.pl
use Smart::Args;

&hoge(piyo => 1, fuga => "fuga"); # 文字を渡している(fuga)

sub hoge {
    args my $piyo, my $fuga => 'Int';

    printf "piyo: %d, fuga: %d\n", $piyo, $fuga;
}
```

```shell:実行結果
'fuga': Validation failed for 'Int' with value fuga at C:/Strawberry/perl/site/lib/Smart/Args.pm line 179.
        Smart::Args::_validate_by_rule("fuga", 1, "fuga", "Int", SCALAR(0x29fd990)) called at C:/Strawberry/perl/site/lib/Smart/Args.pm line 63
        Smart::Args::args(1, undef, "Int") called at sample.pl line 6
        main::hoge("piyo", 1, "fuga", "fuga") called at sample.pl line 3
```

どのような型がチェックできるかというと・・・

```
Any
 Item
     Bool
     Maybe[`a]
     Undef
     Defined
         Value
             Str
                 Num
                     Int
                 ClassName
                 RoleName
         Ref
             ScalarRef
             ArrayRef[`a]
             HashRef[`a]
             CodeRef
             RegexpRef
             GlobRef
                 FileHandle
             Object
```
https://metacpan.org/pod/Mouse::Util::TypeConstraints#Default-Type-Constraints

いろいろありますね！

さきほどまでの例だと第2引数しか型チェックできていません。
すべての引数をチェックしたいときは、それぞれに型の指定が必要です。

```perl
args my $hoge => 'Int', my $fuga => 'Int';
```

#### 任意なキー

渡すかどうかわからない引数ってありますよね。
そんなときに使える`optional`というのが用意されています。

```perl
use Smart::Args;

&hoge(fuga => 1, piyo => 2); # ok
&hoge(fuga => 1); # ok

sub hoge {
    args my $fuga, my $piyo => {optional => 1};
    printf "ok\n";
}
```

```shell:結果出力
ok
ok
```

引数が渡されなかった変数(上の場合だと2回目の`$piyo`)、値は`undef`となります。

#### 初期値

指定されなかったら初期値入れておいてほしいというときもあると思います。
そんなときには`default`を使います。

```perl
use Smart::Args;

&hoge(fuga => 1, piyo => 3); # fuga: 1, piyo: 3
&hoge(fuga => 1); # fuga: 1, piyo: 2

sub hoge {
    args my $fuga, my $piyo => {default => 2};

    printf "fuga: %d, piyo: %d\n", $fuga, $piyo;
}
```

```shell:結果出力
fuga: 1, piyo: 3
fuga: 1, piyo: 2
```

defaultを使用した場合、常に値が入った状態になるので指定しなくても大丈夫になります。

#### 型の指定

`my $hoge => 'Int'`というように型をチェックしていましたが、
他のオプションと一緒に値の型チェックもしたいというときは、`isa`を使います。

```perl:sample.pl
use Smart::Args;

&hoge(fuga => 1, piyo => "foo"); # piyoが文字になっている

sub hoge {
    args my $fuga, my $piyo => {isa => 'Int', default => 2};

    printf "piyo: %d, fuga: %d\n", $piyo, $fuga;
}
```

```shell:結果出力
'piyo': Validation failed for 'Int' with value foo at C:/Strawberry/perl/site/lib/Smart/Args.pm line 179.
        Smart::Args::_validate_by_rule("foo", 1, "piyo", HASH(0xf3bec0), SCALAR(0x2a7e9c0)) called at C:/Strawberry/perl/site/lib/Smart/Args.pm line 63
        Smart::Args::args(1, undef, HASH(0xf3bec0)) called at sample.pl line 6
        main::hoge("fuga", 1, "piyo", "foo") called at sample.pl line 3
```

ハッシュ

```perl

# HashRef
&ex_hash(hoge => {});

sub ex_hash {
    args my $hoge => {isa => 'HashRef'};
}

# ArrayRef
&ex_hash_array(piyo => []);

sub ex_hash_array {
    args my $piyo => {isa => 'ArrayRef'};
}
```

配列内の型をチェック

```perl
&hash_array(fuga => ['Hoge', 'Piyo']);

sub hash_array {
    args my $fuga => {isa => 'ArrayRef[Str]'};
}
```

package名をチェック

```perl
my $hoge = Hoge->new;
$hoge->piyo;

package Hoge;
sub piyo {
    args my $self => {isa => 'Hoge'};
}
```

値が undef or 型 なとき

```perl
my $value = "abc";
# my $value = undef;

&fuga(hoge => $value);

sub fuga {
    args my $hoge => {isa => 'Maybe[Str]'}; # Str or undef
}

```

### args_pos

名前つきでない引数をチェックする

```perl
use Smart::Args;

&hoge(1, 2);

sub hoge {
    args_pos my $piyo, my $fuga;

    printf "piyo: %d, fuga: %d\n", $piyo, $fuga;
}
```
```shell:結果出力
piyo: 1, fuga: 2
```

先程のコードと変わったところはあまりないですねw

これも先程と同じような型チェックなどができます。
もちろんご紹介した`optional`・`defalut`・`isa`も同じように使用できます。

型チェックでオブジェクトから呼ばれているかチェックするなら・・・

```perl:sample.pl
{
    package Hoge;
    use Smart::Args;
    use Object::Simple -base;

    sub piyo {
        args_pos my $self => 'Object', my $fuga => 'Str';
        printf "%s\n", $fuga;
    }
}

my $hoge = Hoge->new;
$hoge->piyo("fuga!"); # OK

Hoge::piyo("fuga!") # NG
```

```shell:結果出力
fuga!
'self': Validation failed for 'Object' with value fuga! at C:/Strawberry/perl/site/lib/Smart/Args.pm line 179.
        Smart::Args::_validate_by_rule("fuga!", 1, "self", "Object") called at C:/Strawberry/perl/site/lib/Smart/Args.pm line 107
        Smart::Args::args_pos("fuga!", "Object", undef, "Str") called at sample.pl line 7
        Hoge::piyo("fuga!") called at sample.pl line 15
```

とこんな感じで`Object`と指定してあげれば、
オブジェクト経由で呼ばれているのをチェックできます。

## まとめ

`Smart::Args`を使って簡単＆楽に引数チェックをいれていきましょう！