前回の
Type::Tinyを用いた型制約
型制約とは期待している値の範囲を表し、Numという型制約であれば数値全体を表します。できてよいことだけできるのが、
ここでは、Type::Tiny 1.
Type::Tiny ── Perlのモダンな型制約
Type::Tinyは型制約とその周辺ツールを同封したモジュールで、Dancer2やGraphQLの型制約として利用されています。
Type::Tinyには、
- ポータビリティが高い
Moose、Mouse、Mooなどのクラスビルダの型制約と互換性がある- 基本コアモジュールのみに依存している
- 定義済み型制約ライブラリの使い勝手が良い
- パフォーマンスが十分良い
- ピュアPerl実装の
Type::Tinyでも、Sub::Quote::quote_で型の判定をインライン展開し、sub 速い Type::Tiny::XS、Ref::Util::XSなどXSモジュールで必要に応じてスピードアップできる
- ピュアPerl実装の
まずは、Type::Tinyを直接使ってみましょう。次のコードは偶数の型制約を作っています。偶数判定するコードリファレンスを渡し、$_は判定したい値です。
use Type::Tiny;
my $even = Type::Tiny->new(
constraint => sub { $_ % 2 == 0 },
);
$even->check(3); # => not ok
$even->check(4); # => okType::Tinyは表2の判定メソッドを持ち、Mooseと似た使い勝手になっています。
| メソッド | 説明 |
|---|---|
check($value) : Bool | 型制約を満たすか否か真偽値を返す |
validdate($value) : Maybe(Str) | 型制約を満たさなければエラーメッセージを返す |
assert_ | 型制約を満たさなければdie |
get_ | 与えられた値に対するエラーメッセージ。型制約を満たすか否かは問わない |
Type::Libraryで、型制約を再利用可能にする
Type::Tinyと同時にインストールされるモジュールに、Type::Library、Type::Utils、Types::Standardがあります。Type::Libraryを利用すれば、Type::Utilsはdeclare、as、where、enumといった型制約定義のためのDSLTypes::Standardは、Int、Strといった基本的な型制約を提供します。これらのモジュールを利用することで、
次のコードは、
package MyType;
use strict;
use warnings;
use Type::Library -base;
use Type::Utils;
use Types::Standard -types;
# 偶数
declare 'Even',
as Int,
where { $_ % 2 == 0 };
# 血液型
enum 'Blood', ['A', 'B', 'AB', 'O'];
1;型制約Even、Bloodは、
use Test::More;
use MyType qw(Even Blood);
ok not Even->check(3);
ok Even->check(4);
ok Blood->check('A');
ok not Blood->check('C');
done_testing;エクスポートオプションはほかにも豊富にあるので、Type::Libraryのドキュメントを参照してください。
DSLを使わずType::Libraryを利用する
理解のために、
package MyTypeWithoutDSL;
use strict;
use warnings;
use Type::Library -base;
use Type::Tiny;
use Types::Standard qw(Int Str);
__PACKAGE__->meta->add_type(
Type::Tiny->new(
name => 'Even',
parent => Int,
constraint => sub { $_ % 2 == 0 }
)
);
__PACKAGE__->meta->add_type(
Type::Tiny->new(
name => 'Blood',
parent => Str,
constraint => sub { m!\A(?:A|B|AB|O)\z! }
)
);
__PACKAGE__->meta->make_immutable;Type::Libraryは、__に型制約の情報を保存し、Type::Utilsはこのメタオブジェクトを隠蔽するDSLを提供していることがわかります。たとえば、__で定義した型制約の一覧を取り出すことができます。
定義済み型制約ライブラリ
既存の定義済みの型制約ライブラリを紹介します。独自の型制約を定義する際、
Types::Standard──組み込みの基本的な型制約ライブラリ
よく使う型制約を詰め合わせしたTypes::Standardの利便性は高いです。これもType::Tinyに同封されます。Types::Standardは、Moose、Mouseと同様に提供します。
Any
Item
Bool
Maybe[`a]
Undef
Defined
Value
Str
Num
Int
ClassName
RoleName
Ref
ScalarRef[`a]
ArrayRef[`a]
HashRef[`a]
CodeRef
RegexpRef
GlobRef
FileHandle
Objectこの基本型と組み合わせ、
以下は、Map、Dict、Tupleの例です。JSONのような構造の型制約を作る場合に便利です。
# HashRef のキー、値が、Str、Int か
my $Map = Map[Str, Int];
ok $Map->check({ a => 1, b => 2 });
ok not $Map->check({ a => 1, b => 'aaa' });
# HashRefで、かつ
# nameキーに対する値がStr、ageキーに対する値がInt
my $Dict = Dict[name => Str, age => Int];
ok $Dict->check({ name => 'foo', age => 2 });
ok not $Dict->check({ name => 'bar', age => 'AA' });
# Optional で一部のキーがなくてもよい
{
my $Dict = Dict[name => Str, id => Optional[Int]];
ok $Dict->check({name => 'foo', id => 1});
ok $Dict->check({name => 'bar'});
ok not $Dict->check({name => 'bar', id => 'AAA'});
}
# ArrayRef で値がそれぞれ Str, Int
my $Tuple = Tuple[Str, Int];
ok $Tuple->check(['foo', 1]);
ok not $Tuple->check(['foo', 'aaa']);
ok not $Tuple->check(['foo', 1, 123]);次に、InstanceOfとHasMethodsの利用例です。HasMethodsはダックタイピングに利用します。
# Foo または Bar のインスタンスか否か
my $InstanceOf = InstanceOf['Foo', 'Bar'];
ok $InstanceOf->check(bless {}, 'Foo');
ok $InstanceOf->check(bless {}, 'Bar');
ok not $InstanceOf->check(bless {}, 'Baz');
# check, get_message メソッドを持つか
my $HasMethods = HasMethods['check', 'get_message'];
{
use Type::Tiny;
use Mouse::Meta::TypeConstraint;
ok $HasMethods->check(Type::Tiny->new);
ok $HasMethods->check(
Mouse::Meta::TypeConstraint->new
);
}続いて、Enum、Overload、Tiedの例です。tieを用いることで変数と型制約を結び付け、
# Hoge, Fugaのうちのいずれか
my $Enum = Enum['Hoge','Fuga'];
ok $Enum->check('Hoge');
ok $Enum->check('Fuga');
ok not $Enum->check('Boo');
# 指定した演算子がオーバーロードされているか
my $Overload = Overload['&','|','~','>','<'];
ok $Overload->check(Type::Tiny->new);
# tie されているか否か / 値に型制約をtieできる
tie my $tiestr, Str;
ok \$tiestr ~~ Tied;
$tiestr = 'hello'; # ok
eval { $tiestr = {} }; # die
ok $@;そのほかの型制約ライブラリ
型制約ライブラリには、
- Types::Common::String
- 文字列関連の型制約
- UpperCaseStr、
LowerCaseStrといった大文字、 小文字の型制約 - StrLength[min, max]といった文字列長による型制約
- Types::Common::Numeric
- 数字関連の型制約
- PositiveNum、
NegativeNum、 PositiveInt、 NegativeIntといった正負の型制約 - NumRange[min,max]、
IntRange[min,max]といった数値区間の型制約
いくつもの型制約のライブラリをuseすることは手間ですので、Type::Utils#extendsでアプリケーションで利用する型制約をまとめておくと便利です。
package MyTypeExtended;
use Type::Library -base;
use Type::Utils -all;
BEGIN {
extends qw(
Types::Standard
Types::Common::Numeric
Types::Common::String
);
}
declare 'MyRange',
as StrLength[0,191],
our @EXPORT = __PACKAGE__->type_names;
1;これで、MyRangeもTypes::StandardのStrも、MyTypeExtendedをuseするだけで利用できます。
use MyTypeExtended -types;
Str->check('foo');
MyRange->check('bar');型制約のライブラリには、Types::URI、Types::Path::Tiny、Types::UUIDといった用途がはっきりしたものもあります。たとえばTypes::URIであれば文字列からURIオブジェクトへの暗黙的な変換が行え、URIオブジェクトへの変換を省け、
<続きの
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT

