Perl Hackers Hub

第40回Perl開発への動的な型制約の導入(2)

前回の(1)こちらから。

Mouse::Util::TypeConstraintsで独自の型を定義する

(1)で、Mouse::Util::TypeConstraintsを利用して独自に型を定義できると紹介しました。⁠2)でその方法について解説します。

Mouse::Util::TypeConstraintsは、自分で独自に型を定義するユーティリティを提供してくれるモジュールです。このモジュールはMouseというディストリビューションに含まれているため、Mouseをcpanmでインストールすることで利用できます。cpanmコマンドでインストールし、正しくロードできればインストールは成功しています。

$ cpanm Mouse
$ perl -MMouse -E 'say $Mouse::VERSION'

独自の型を定義するユーティリティはいくつもあるため、その中で代表的なものを紹介します。そのほかの方法は、Mouse::Util::TypeConstraintsのドキュメントを参照してください。

subtypeを使った既存の型の拡張

subtypeを利用すれば、デフォルトで定義されている型や独自で定義した型から派生し、拡張できます。subtypeはDSLDomain Specific Languageドメイン特化言語)のように利用できます。渡せるオプションは次のとおりです。

as
どの型から派生するか
where
その型が満たすべき制約。Perlのコードであればどんなものでも書ける。書いたコードが真を返せば制約を満たし、偽を返せば制約を満たさない型を定義できる
message
制約を満たさなかった場合のエラーメッセージ

次のコードは、正の整数であるNatural型を定義するコードです。

use Mouse::Util::TypeConstraints;
use Smart::Args;

// (1)型定義
subtype 'Natural'
    => as 'Int'
    => where { $_ > 0 }
    => message { "This number($_) is not Natural" };

sub func {
    args my $p => 'Natural';
}

func(p => 3);  ━(2)
func(p => -1);  ━(3)
func(p => 'aiueo');  ━(4)

(1)のようにsubtypeを用いて型を定義します。wheremessageの中では$_という変数が利用でき、中には型を利用した関数の引数に渡された値が代入されています。(2)の場合は引数が正の整数なので正常に実行できます。(3)の場合は引数が負の整数なので、messageで定義した'p': This number(-1) is not Naturalが出力され、エラーで終了します。(4)ではそもそもNaturalが継承しているInt型ですらないので、この場合もエラーとなります。

Natural型からさらに派生し、5未満の正の整数という型NaturalLessThanFiveの定義もできます。

use Mouse::Util::TypeConstraints;
subtype 'NaturalLessThanFive'
    => as 'Natural'
    => where { $_ < 5 }
    => message { "$_ is not less than five" };

whereにはPerlのコードならどんなものでも書けるので、メールアドレスとして妥当である型EmailAddressを定義できます。CPANで公開されているEmail::Valid::Looseモジュールを使って次のように書きます。

use Mouse::Util::TypeConstraints;
use Email::Valid::Loose;

subtype 'EmailAddress',
    => as 'Str'
    => where {
        Email::Valid::Loose->address(-address => $_)
    }
    => message { "$_ is not a valid email address" };

enumを使った型の定義

関数の引数に対して、列挙した文字列のどれかしか受け付けないように制約をかけたい場合があります。enumを利用すれば、そのような制約をかけるための型を定義できます。

次のコードは、色として赤red⁠・blue⁠・greenのどれかしか受け付けない型を定義する例です。

use Smart::Args;
use Mouse::Util::TypeConstraints;

enum 'RGBColor' => ['red', 'blue', 'green'];

sub func {
    args my $p => 'RGBColor';
}
func(p => 'red'); # ok
func(p => 'black'); # error

duck_typeを使った型の定義

duck_typeを利用すると、指定したメソッドを持つオブジェクトのみを受け入れる型を定義できます。

次のコードは、idメソッドとas_stringメソッドの両方を持つクラスのオブジェクトを受け入れる型を定義する例です。

use Mouse::Util::TypeConstraints;
duck_type 'HasIdAndAsStringMethod' => [qw(id as_string)];

型を定義するときの注意点

独自の型を定義する際、一つ注意点があります。それは、定義する型はグローバルな名前空間に作られることです。別々のパッケージで型を定義したとしても、名前が同じなら両方を読み込んだ際にコンフリクトし、あとから読み込まれたほうに上書きされます。

ドキュメントによると、特定のパッケージで型を定義する場合、パッケージ名を含めた型名にすることが推奨されています。たとえばBlogパッケージでブログが取り得る状態を型として定義するなら、次のようにBlog::Statusという名前で定義します。

package Blog;
use Mouse::Util::TypeConstraints;
enum 'Blog::Status' => [qw(public private)];

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

WEB+DB PRESS

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

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧