本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは、
本稿のサンプルコードは、
なぜ動的な型制約を導入したいのか
動的な型制約とは、
動的な型制約をPerlへ導入する方法の解説に入る前に、
型がないことによる問題
Perlを使うメリットにはたとえば次のものがあります。
- 枯れている言語でドキュメントも多く、
安定して利用できる - CPANモジュールにたくさんの資産があり、
かつそれらのドキュメントは同じような形式で書かれていて扱いやすい - さまざまな環境にデフォルトで実行環境が入っている
- 動的言語でコンパイルがなく、
型を意識する必要がないため、 学習コストが比較的小さく、 すばやく開発できる
しかし、
- 2つの引数を必要としている関数に1つしか引数を渡さなかった
- Blogクラスのオブジェクトを必要としているところに、
別のクラスのオブジェクトを渡した
エラーが起こるまで実行を進めることは、
リスト1は、func
は1つ目の引数に文字列を、func
内では最初に$string
を使った処理を、$number
を使った処理を行います。しかし、
sub func {
my ($string, $number) = @_;
// (1)$stringを使った処理
...
// (2)$numberを使った処理
...
}
// (3)引数を1つしか渡していない
func('aiueo');
// (4)2つ目の引数に数字以外を渡す
func('aiueo', 'abcde');
動的な型制約での解決
この問題を解決するにはどうすればよいでしょうか。
一つの方法は使用する言語を静的言語に変えることです。しかし、
言語を変えずにPerlのまま問題を解決する方法として、
リスト1の例なら、func
関数は文字列と数字の2つの引数を必要とする」
このように実行時に引数の型をチェックすることによって、
Smart::Argsで引数に型制約を導入する
それでは、
関数の引数に対して動的な型制約をかけるためのCPANモジュールには、
Smart::Argsは自分で定義した関数の最初に簡潔な記法を記述することで、
Smart::Argsは、args
とargs_
という2つの記法の使い方と、
Smart::Argsのインストール
Smart::ArgsはCPANモジュールとして提供されているので、cpanm
コマンドでインストールします。モジュールをロードできれば、
$ cpanm Smart::Args
$ perl -MSmart::Args -E 'say $Smart::Args::VERSION'
Smart::Argsの基本的な使い方
さっそくSmart::Argsを使って、args
記法や、args_
記法などを紹介します。
args──名前付き引数をチェックする
まずargs
という記法を使い、func(number => 1, string => 'abc')
のように、number
やstring
のような名前を付けて渡すものです。
たとえば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 と出力される
もし関数の使い方を間違えて、func
関数のnumber
という引数に、
func(number => 1.5, string => 'aiueo');
この場合、'number': Validation failed for 'Int'with value 1.
のように、number
という引数はInt
を期待していたが1.func
関数を実行せずに終了します。
Smart::Argsで型制約をかけた場合、string
という名前の引数を渡し忘れた例です。
func(number => 10);
この場合も、missing mandatory parameter named'$string'
のように、string
という引数は必須だが渡されていない」func
関数を実行せずに終了します。
args
記法に渡せる型には、Int
やStr
などさまざまなものがあります。指定できる型について詳しくは、
default──引数を渡さない場合のデフォルト値を設定する
先ほど、
オプションを利用したい場合は、Int
などの文字列を渡していた部分にハッシュを渡します。型はisa
に記述し、default
に記述します。
次のコードは、p
という引数を渡さなかった場合、
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
パッケージにクラスメソッドとインスタンスメソッドを定義した例です。メソッドに型制約を導入する場合は、$class
もしくは$self
という変数名を使って定義します。このようにすることで、$class
や$self
の変数に、ClassName
を使えます。
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_
を利用します。記法の名称が変わっただけで、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からデフォルトで提供されている代表的な型には、
型名 | 意味 |
---|---|
Bool | 真偽値を受け付ける型 |
Int | 整数を受け付ける型 |
Num | 小数も含めた数字を受け付ける型 |
Str | 文字列を受け付ける型。ただし、 |
ArrayRef[type] | 配列リファレンスを受け付ける型。typeに型を指定すると、 |
HashRef[type] | ハシュリファレンスを受け付ける型。typeに型を指定すると、 |
Maybe[type] | typeとして指定した型、 |
クラス名を型として指定すると、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というモジュールを利用すれば、
ここまでで紹介したさまざまな型を利用すれば、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);
<続きの
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT