Perl Hackers Hub

第52回 Perlで堅牢な開発―構文チェック,静的検査,型制約(2)

この記事を読むのに必要な時間:およそ 4 分

定義済み型制約ライブラリ

既存の定義済みの型制約ライブラリを紹介します。独自の型制約を定義する際,基本的には定義済みの型制約の組み合わせで表現することになります。

Types::Standard─⁠─組み込みの基本的な型制約ライブラリ

よく使う型制約を詰め合わせしたTypes::Standardの利便性は高いです。これもType::Tinyに同封されます。Types::Standardは,まずPerl 5の基本型制約をMooseMouseと同様に提供します。

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

この基本型と組み合わせ,ほかに構造やオブジェクトなどの型制約も利用できます。

以下は,MapDictTupleの例です。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]);

次に,InstanceOfHasMethodsの利用例です。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
    );
}

続いて,EnumOverloadTiedの例です。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;

これで,自前で用意したMyRangeTypes::StandardStrも,MyTypeExtendeduseするだけで利用できます。

use MyTypeExtended -types;

Str->check('foo');
MyRange->check('bar');

型制約のライブラリには,Types::URITypes::Path::TinyTypes::UUIDといった用途がはっきりしたものもあります。たとえばTypes::URIであれば文字列からURIオブジェクトへの暗黙的な変換が行え,わかりきったURIオブジェクトへの変換を省け,型制約に振り回されにくくなります。こういった変換を,型強制と呼びます。

<続きの(3)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.110

2019年4月25日発売
B5判/168ページ
定価(本体1,480円+税)
ISBN978-4-297-10533-4

  • 特集1
    名前付け大全
    設計も,実装も,ここから始まる!
  • 特集2
    速習gRPC
    高速! 安全! 高信頼! マイクロサービス接続の大本命
  • 特集3
    最新TLS 1.3徹底解剖
    通信を覗いてHTTPSの裏側を知る
  • 一般記事
    オープンデータのためのWikidata入門
    SPARQLで知識データベースを自在に検索!

著者プロフィール

小林謙太(こばやしけんた)

東北大学大学院数学専攻修了後,2011年に株式会社モバイルファクトリーに新卒入社。

ソーシャルゲームの開発やプロダクトマネジメントに従事したのち,現在は,育成・採用・広報などのエンジニア組織の課題解決に取り組む。また,Japan Perl Associationのメンバーとして,YAPC::JapanやGotanda.pmなどのコミュニティを中心に活動している。