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

今回は、Smart::Args をより活用する方法をご紹介します。

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

## 基本的な使い方

基本的な使い方の詳細は、前回の記事をご覧ください。

https://qiita.com/hiro_nico/items/9106054af471cf147623

`args` or `args_pos` で引数の型を指定すると、型チェックしてくれるモジュールです。

```perl
use strict;
use warnings;
use utf8;
use Smart::Args;
# args: 名前付きでの指定
hoge(msg => "hello");
sub hoge {
    args my $msg => 'Str';
    print $msg;
}
# args_pos: 指定順での指定 
fuga(2);
sub fuga {
    args_pos my $arg => 'Int';
    print $arg;
}
```

今回は、さらに様々なパターンの活用方法を記載します。

## 型の種類

様々な種類の型を指定することができます。

| 型名 | 説明 |
|:-----------|:-----------|
| Any |  |
| 　Item |  |
| 　　Bool | 0 or 1 or "" or undef |
| 　　Maybe['a] | undef or 型名 |
| 　　Undef | undef (not Defined) |
| 　　Defined | defined |
| 　　　Value |  |
| 　　　　Str | 文字(数字も含む) |
| 　　　　　Num | 小数も含む数値 |
| 　　　　　　Int | 整数のみ |
| 　　　　　ClassName | ex: `Hoge::Fuga` |
| 　　　　　RoleName | ex: `Hoge::Fuga::Role` |
| 　　　Ref |  |
| 　　　　ScalarRef |  ex: `$scalar = \(my $var);` |
| 　　　　ArrayRef['a] |  ex: `$array = [ 1, 2 ];` |
| 　　　　CodeRef |  ex: `$code = sub{};` |
| 　　　　RegexpRef |  ex: `$regex = qr/^hoge$/;` |
| 　　　　GlobRef |  ex: `my $GLOB       = *GLOB_REF;`[[br]]　　`my $GLOB_REF   = \$GLOB;` |
| 　　　　　FileHandle |  ex: `open(my $fh, '<', "hoge.txt")` |
| 　　　　Object |  ex: `bless {}, 'Hoge';` |

※`'a`には型名が入る
引用: https://metacpan.org/pod/Mouse::Util::TypeConstraints#Default-Type-Constraints

## 指定方法

2つの方法があります。(`args`, `args_pos`共通)

* 直接指定する

```perl
sub hoge {
    args_pos my $msg => 'Str';
    print $msg;
}
```

* `isa`を使って指定する

```perl
sub hoge {
    args_pos my $msg => {isa => 'Str'};
    print $msg;
}
```

`isa`は、主に他のオプションと一緒に利用したいときに使います。
また 複数の引数を指定するときは、`,`でつなげるだけでOKです。(`isa`も同様)

```perl
sub hoge {
    args_pos my $msg => 'Str', my $name => 'Str';
    printf "%s (%s)", $msg, $name;
}
```

型をチェックしないものは、なにも指定しない。

```perl
sub hoge {
    args_pos my $self, my $msg => 'Str';
    printf "%s (%s)", $msg, $name;
}
```

## 型の中身もチェックする

先ほど出てきた `'a` の紹介です。
`'a`には、`Str`などの型を定義します。
下記の例で、配列内に数字が以外が入っていた場合にエラーとしたいとき

```perl
use strict;
use warnings;
use utf8;
use Smart::Args;
my $array = [1, "に"];
hoge($array);
sub hoge {
    args_pos my $arg => 'ArrayRef'; # ArrayRef なので OK
    print join " ", @$arg;
}
```

```text:実行結果
1 に
```

上記の場合は、問題なく実行される。
`ArrayRef`の中身を指定してあげると・・・

```perl
use strict;
use warnings;
use utf8;
use Smart::Args;
my $array = [1, "に"];
hoge($array);
sub hoge {
    args_pos my $arg => 'ArrayRef[Int]'; # ArrayRef内に数字(Int)以外が含まれていたので NG
    print join " ", @$arg;
}
```

```text:実行結果
'$arg': Validation failed for 'ArrayRef[Int]' with value ARRAY(0x90a9dc) at C:/strawberry/perl/site/lib/Smart/Args.pm line 179.
        Smart::Args::_validate_by_rule(ARRAY(0x90a9dc), 1, "\$arg", "ArrayRef[Int]") called at C:/strawberry/perl/site/lib/Smart/Args.pm line 129
        Smart::Args::args_pos(undef, "ArrayRef[Int]") called at hoge.pl line 11
        main::hoge(ARRAY(0x90a9dc)) called at hoge.pl line 8
```

と表示されエラーとなる。

## 値を指定したときだけチェックする

`optional`や`Maybe[]`を使うと、引数が指定されたときにチェックできます。

```perl
use strict;
use warnings;
use utf8;
use Smart::Args;
hoge(1);    # OK
hoge(2, 2); # OK
hoge(3, "さん"); # NG
sub hoge {
    args_pos my $no => 'Int', my $mode => {isa => 'Int', optional => 1};
    if (defined $mode) {
        printf "%d (mode: %d)\n", $no, $mode;
    } else {
        printf "%d\n", $no;
    }
}
```

```text:実行結果
1
2 (mode: 2)
'$mode': Validation failed for 'Int' with value さん at C:/strawberry/perl/site/lib/Smart/Args.pm line 179.
        Smart::Args::_validate_by_rule("\x{3055}\x{3093}", 1, "\$mode", HASH(0x69a9ec)) called at C:/strawberry/perl/site/lib/Smart/Args.pm line 129   
        Smart::Args::args_pos(3, "Int", undef, HASH(0x69a9ec)) called at hoge.pl line 13
        main::hoge(3, "\x{3055}\x{3093}") called at hoge.pl line 10
```

`undef`はOKとするとき(4)

```perl
use strict;
use warnings;
use utf8;
use Smart::Args;
fuga(4, undef);  # OK
fuga(5, 5);      # OK
fuga(6, "ろく"); # NG
sub fuga {
    args_pos my $no => 'Int', my $mode => 'Maybe[Int]';
    if (defined $mode) {
        printf "%d (mode: %d)\n", $no, $mode;
    } else {
        printf "%d\n", $no;
    }
}
```

```text:実行結果
4
5 (mode: 5)
'$mode': Validation failed for 'Maybe[Int]' with value ろく at C:/strawberry/perl/site/lib/Smart/Args.pm line 179.
        Smart::Args::_validate_by_rule("\x{308d}\x{304f}", 1, "\$mode", "Maybe[Int]") called at C:/strawberry/perl/site/lib/Smart/Args.pm line 129     
        Smart::Args::args_pos(6, "Int", undef, "Maybe[Int]") called at hoge.pl line 13
        main::fuga(6, "\x{308d}\x{304f}") called at hoge.pl line 10
```

第2引数に文字列を指定していることでエラーがでている。

## デフォルト値を設定する

`default`を使用すると、指定されていないときの初期値を設定できます。

```perl
use strict;
use warnings;
use utf8;
use Smart::Args;
hoge(1); # no のみ指定する
sub hoge {
    args_pos my $no => 'Int', my $num => {isa => "Int", default => 2};
    printf "no: %d, num: %d\n", $no, $num;
}
```

```text:実行結果
no: 1, num: 2
```

no のみしか値を指定していないが、numにも値(指定したデフォルト値)が入っている。

## まとめ

Smart::Argsをより活用することによって、
引数チェックするコードを書かなくてもチェックできるようになり
きれいにコードが仕上がります。

Perlは型が緩い部分もあるので、Smart::Argsを活用して型チェックしていきましょう。