Perl Hackers Hub

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

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

Smart::Argsの基本的な使い方

さっそくSmart::Argsを使って,関数に動的な型制約を付けてみましょう。ここでは名前付き引数をチェックするargs記法や,その記法に渡すことができるオプション,名前付き引数ではなく通常の引数で受け取るためのargs_pos記法などを紹介します。

args─⁠─名前付き引数をチェックする

まずargsという記法を使い,関数の名前付き引数に対して動的な型制約をかけてみます。名前付き引数とは,func(number => 1, string => 'abc')のように,引数にnumberstringのような名前を付けて渡すものです。

たとえばfuncという関数があり,引数としてnumberという名前で整数を,stringという名前で文字列を受け取りたいとします。そのように型制約を付けたい場合,次のように関数を定義し,args記法で制約を書きます。

use Smart::Args;

sub func {
    args my $number => 'Int',
         my $string => 'Str';

    print(sprintf('%d:%s', $number, $string));
}

定義したfunc関数を正しい引数で呼び出すと,numberという名前で渡した引数は$numberに,stringという名前で渡した引数は$stringに/代入され,関数内で利用できます。

func(number => 10, string => 'aiueo');
# 10:aiueo と出力される

もし関数の使い方を間違えて,渡した引数の型が制約を満たさない場合,Smart::Argsはその関数を実行せずに例外を出します。どう間違えているかは標準エラー出力にエラーメッセージとして表示されます。たとえば次のコードは,func関数のnumberという引数に,間違えて整数ではなく小数1.5を渡したものです。

func(number => 1.5, string => 'aiueo');

この場合,'number': Validation failed for 'Int'with value 1.5のように,numberという引数はIntを期待していたが1.5という値が指定されている」というエラーを出して,func関数を実行せずに終了します。

Smart::Argsで型制約をかけた場合,デフォルトでは定義した引数が渡されないときもエラーを発生させます。次のコードは,stringという名前の引数を渡し忘れた例です。

func(number => 10);

この場合も,missing mandatory parameter named'$string'のように,stringという引数は必須だが渡されていない」というエラーを出して,func関数を実行せずに終了します。

args記法に渡せる型には,IntStrなどさまざまなものがあります。指定できる型について詳しくは,⁠制約として指定できる型」で後述します。

default─⁠─引数を渡さない場合のデフォルト値を設定する

先ほど,Smart::Argsでは引数を渡さなかった場合もエラーを発生させると紹介しました。しかし型制約の定義時にオプションを利用すれば,渡さなかった場合のデフォルト値を設定できます。

オプションを利用したい場合は,型制約としてIntなどの文字列を渡していた部分にハッシュを渡します。型はisaに記述し,デフォルト値はdefaultに記述します。

次のコードは,pという引数を渡さなかった場合,デフォルト値として10を利用する例です。

use Smart::Args;
sub func_with_default {
    args my $p => { isa => 'Int', default => 10 };
    print($p);
}

func_with_default(p => 5);
# 5と出力される
func_with_default();
# 10と出力される
クラスメソッド,インスタンスメソッドに型制約を付ける

今までは関数に型制約をかけてきましたが,クラスメソッドやインスタンスメソッドに型制約を付けることもできます。

リスト2は,Fooパッケージにクラスメソッドとインスタンスメソッドを定義した例です。メソッドに型制約を導入する場合は,(1)(2)のように,1つ目の型制約を$classもしくは$selfという変数名を使って定義します。このようにすることで,$class$selfの変数に,クラスメソッドとして呼び出した場合はクラス名が,インスタンスメソッドとして呼び出した場合はそのオブジェクトが代入されます。また,クラス名であるという型制約にはClassNameを使えます。

リスト2 メソッドに型制約を付ける

package Foo;
use Smart::Args;

sub new {
    my ($class) = @_;
    return bless {}, $class;
}

sub class_method {
    args my $class => 'ClassName', ━(1)
         my $p => 'Int';
}

sub instance_method {
    args my $self, ━(2)
         my $q => 'Str';
}

Foo->class_method(p => 3);
my $foo = Foo->new;
$foo->instance_method(q => 'abc');
args_pos─⁠─通常の引数をチェックする

argsでは名前付き引数を取り扱いました。しかし名前付き引数ではなく,もっとシンプルにfunc(3,'abc')のように通常の引数として受け取りたい場合もあります。このときはargs_posを利用します。記法の名称が変わっただけで,使い方はargsと同じです。

use Smart::Args;

sub func_with_args_pos {
    args_pos my $p => 'Int',
             my $q => { isa => 'Str', default => 'aiu' };
    print(sprintf('%d:%s', $p, $q));
}

func_with_args_pos(3, 'abc');
# 3:abcと出力される

制約として指定できる型

ここでは,型制約として指定できる型について紹介します。

Smart::Argsは内部でCPANモジュールのMouseの型のしくみを利用しており,次のものを型として指定できます。

  • Mouseからデフォルトで提供されている型
  • クラス名
  • Mouse::Util::TypeConstraintsを利用して,自分で独自に定義した型

Mouseからデフォルトで提供されている代表的な型には,表1のものがあります。それ以外のデフォルトで提供されている型についてはMouse::Util::TypeConstraintsのドキュメント「Default Type Constraints」を参照してください。

表1 代表的な型とその意味

型名意味
Bool真偽値を受け付ける型
Int整数を受け付ける型
Num小数も含めた数字を受け付ける型
Str文字列を受け付ける型。ただし,Perlでは整数や小数も文字列として受け付けるので注意
ArrayRef[type]配列リファレンスを受け付ける型。typeに型を指定すると,type型の配列リファレンスという制約をかけられる。たとえばArrayRef[Int]とすれば整数の配列を受け付ける
HashRef[type] ハシュリファレンスを受け付ける型。typeに型を指定すると,そのハッシュリファレンスのvalueの型に制約をかけられる
Maybe[type]typeとして指定した型,もしくはundefを受け付ける型。Maybe[Int]とすれば,整数もしくはundefを受け付ける

クラス名を型として指定すると,そのクラスのオブジェクトおよび,そのクラスを継承したクラスのオブジェクトを渡すことができます。型制約としてURIと指定すると,CPANモジュールとして提供されているURIクラスのインスタンスを渡せます。また,前述したリスト2のように自分でクラスを作った場合も,そのクラス名を型として利用できます。次のコードは,引数としてuriという名前でURIクラスのインスタンスを,fooという名前でFooクラスのインスタンスを受け取る関数を定義する例です。

use URI;
use Smart::Args;
sub args_with_uri_and_foo {
    args my $uri => 'URI',
         my $foo => 'Foo';
}

my $uri = URI->new('http://example.com/');
my $foo = Foo->new;
args_with_uri_and_foo(uri => $uri, foo => $foo);

ほかにもMouse::Util::TypeConstraintsというモジュールを利用すれば,独自に型を定義できます。独自の型の定義方法は次節で紹介します。

ここまでで紹介したさまざまな型を利用すれば,URIクラスのオブジェクトの配列もしくはundefという型を定義できます。

sub args_with_uri_array {
    args my $uris => 'Maybe[ArrayRef[URI]]';
}

my $uri1 = URI->new;
my $uri2 = URI->new;
# 呼び出しは両方成功する
args_with_uri_array(uris => [ $uri1, $uri2 ]);
args_with_uri_array(uris => undef);

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

WEB+DB PRESS

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

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

  • 特集1
    React/Vue.jsで実践!
    コンポーネント設計
    モダンフロントエンドの構造化と分割の新提案
  • 特集2
    RDBMS徹底比較
    PostgreSQL,MySQL,SQL Server,Oracle Database
  • 特集3
    実践Scala
    オブジェクト指向×関数型
  • 一般記事
    自作キーボードのススメ
    デザイン,配列,打鍵感……自由自在

著者プロフィール

柴崎優季(しばざきゆうき)

1989年福井県生まれ。株式会社はてな所属。

Webアプリケーションエンジニアとして,はてなブログなどのWebサービスの開発に携わってきた。

Twitter:@shiba_yu36
Blog:http://blog.shibayu36.org/