前回の
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); # => ok
Type::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