Types::Standard で利用できる、知っておくと便利な型
概要
Perlで型チェックを行うモジュールに Type::Tiny というものがあります。
Type::Tiny には Types::Standard というライブラリがついてきて、いろいろな型が利用可能になるわけですが、
これはその Types::Standard で利用可能な、個人的に知っていると便利だったりわかりやすいコードがかけていいなーと思う型を紹介する記事です。
Enum[@list]
列挙型です。
引数として渡した文字列のリストのうち、いずれかにマッチする値だけを許容する型です。
use Test2::V0; use Types::Standard qw( Enum ); my $constraint = Enum[qw( green blue red )]; ok $constraint->check('green'); ok !$constraint->check('hoge');
(これはほとんどの人が知ってそう)
Maybe[T]
未定義値あるいは型パラメータ(に相当するもの)として渡した型制約を満たす値を受け付ける型です。
use Test2::V0; use Types::Standard qw( Maybe Int ); my $constraint = Maybe[Int]; # 未定義値か整数を受け付ける型制約 ok $constraint->check(1); ok $constraint->check(undef); ok !$constraint->check([]);
これを使うと何が嬉しいかというと、この型制約でチェックする値はnullableであるということが明示的になることです。
例えば、引数としてユーザーのIDを渡し、DBからユーザーの名前を取得するようなメソッドを書くとします。
DBには指定されたユーザーIDに対応するユーザー名がない場合があるわけですが、 Function::Return と Maybe型 で返り値のチェックをさせると、このように未定義値が文字列が返ってくるメソッドであるということが明示的になります。
use Types::Standard qw( Maybe Int ); use Function::Return; sub get_user_name :Return(Maybe[Str]) { my ($self, $id) = @_; return $self->db->select(user => +{ id => $id }); }
Dict[ $key => $constraint, ... ]
列挙された全てのkeyと対応するvalueの型制約を満たすハッシュリファレンスのみを受け付ける型を作ることができるものです。
ネストさせることも可能です。
例えば、 idというキーがInt型の値を持ち、nameというキーがStr型の値を持つようなハッシュリファレンスのみを受け付ける型は、以下のようにして作ることができます。
use Test2::V0; use Types::Standard qw( Dict Int Str ); my $constraint = Dict[ id => Int, name => Str, ]; ok $constraint->check(+{ id => 0, name => 'anon', }); ok !$constraint->check(+{}); ok !$constraint->check(+{ status => 'success' });
ハッシュリファレンスで作られているデータ構造を受け渡ししているようなサブルーチンとかで使うと、どんなデータを受け渡しているのかが明示的になって読みやすいコードになります。
TypeScriptのオブジェクト型と似たようなものだと思います。
Tuple[...]
タプルです。
ちゃんと説明すると、列挙された全ての型制約を満たすような要素をもつ配列リファレンスのみを受け付ける型を作ることができるものです。
ネストさせることも可能です。
例えば、0, 1番目が数値で、2番目が文字列である配列リファレンスのみを受け付ける型は、以下のようにして作ることができます。
use Test2::V0; use Types::Standard qw( Tuple Num Str ); my $constraint = Tuple[Num, Num, Str]; ok $constraint->check([0.4, 10, 'hoge']); ok !$constraint->check([]); ok !$constraint->check(['foo', 'bar', []]);
順序づけられた組であることを明示的にコードに書きたい場合や、手っ取り早くデータ構造を作りたい時などに便利なのではないでしょうか。
Optional[T]
Dict, Tuple と一緒に使う型で、省略可能な要素を作れます。
例えば先ほどのDict型のnameをキーとする組を省略可能にしたい場合は、次のようになります。
use Test2::V0; use Types::Standard qw( Dict Optional Num Str ); my $dict = Dict[ id => Num, name => Optional[Str], ]; ok $dict->check({ id => 0, name => 'anon', }); ok $dict->check({ id => 0 });
Tuple型の最後の要素を省略可能にしたい場合は次のようになります。
use Test2::V0; use Types::Standard qw( Tuple Optional Num Str ); my $tuple = Tuple[Num, Num, Optional[Str]]; ok $tuple->check([0.4, 10, 'hoge']); ok $tuple->check([0.4, 10]); ok !$tuple->check([]); ok !$tuple->check(['foo', 'bar', []]);
(Optional単独でも使えますが、その場合の動作は未定義のようなので使うべきではありません。)
HasMethods[@methods]
引数として渡された全てのメソッドをもつクラスのインスタンスのみ受け付ける型です。
以下のコードでは do_something メソッドをもつインスタンスのみを受け付ける型制約を作っています。
package Foo { sub new { my $class = shift; bless +{}, $class; } sub do_something {} } package Bar { sub new { my $class = shift; bless +{}, $class; } } use Test2::V0; use Types::Standard qw( HasMethods ); my $constraint = HasMethods['do_something']; ok $constraint->check(Foo->new); ok !$constraint->check(Bar->new);
duck typing を利用してメソッドを呼び出している場所があるメソッドの引数のバリデーションに利用するなどすると、どんな引数を渡せばいいのかが明示的なコードが書けてよいです。
use Type::Params qw( compile ); use Types::Standard qw( HasMethods ); sub hoge { state $check = compile(HasMethods['do_something']); my ($foo) = $check->(@_); $foo->do_something(); ... }
ちなみに、 Types::Util の duck_type 関数で同等のことができます。
どちらを使うかは好みになるでしょうか・・・。
おわりに
これらの型を活用することでよりわかりやすいコードが書けると思うのでぜひ活用してみてください。
あと、 Types::Standard には他にも知っているとためになるような型がいろいろあるので、時間があればよく目を通しておくといいかなとおもいます。
例えばtieとかoverloadをよく使っているコードがあるのなら、tieされている値やoverloadされている値しか受け付けない型みたいなのも利用できます。