動的な型制約の応用例
前節までで、
アプリケーションの仕様を型として定義する
アプリケーションには、
リスト3は、public
か非公開を表すprivate
のどちらかであると定義しています。update_
に変なデータを渡したとしても、
package Blog;
use Smart::Args;
use Mouse::Util::TypeConstraints;
subtype 'Blog::Title', ┓
=> as 'Str' ┃
=> where { ┃
3 <= length($_) && length($_) <= 100; ┃
} ┣(1)
=> message { ┃
"$_ is not valid blog title"; ┃
}; ┛
enum 'Blog::Status' => [qw(public private)]; ━(2)
sub update_blog { ┓
args my $class => 'ClassName', ┃
my $blog_id => 'Int', ┃
my $title => 'Blog::Title', ┣(3)
my $status => 'Blog::Status'; ┃
# 渡されたデータでのブログデータの更新処理 ┃
... ┃
} ┛
この例のように、
型制約でユーザーの入力を制限する
先ほど、
型制約を満たすかどうかを真偽値で取得できれば、find_
という型を取得するユーティリティがあり、
次のコードは、Blog::Title
型とfind_
を利用して、
use Mouse::Util::TypeConstraints;
my $input = ...;
my $is_valid = find_type_constraint('Blog::Title')-
>check($input); ━(1)
if (!$is_valid) {
# ユーザーにエラーメッセージを返す
}
$input
がBlog::Title
型を満たすなら真が、$is_
に代入されます。偽の場合にユーザーにフィードバックを返せば、
型制約で不正なリクエストパラメータを制限する
Webアプリケーションを作っていると、
たとえばブログの状態を公開public
)private
)
<select name="blog_status">
<option value="public">公開</option>
<option value="private">非公開</option>
</select>
このとき、blog_
としてpublic
かprivate
のどちらかしか送られません。しかし悪意のあるユーザーの場合、
このような悪意のあるパラメータをいちいちチェックするのは大変です。チェックをする方法はいろいろありますが、
リスト4は、typed_
というメソッドを定義したものです。typed_
メソッドは、undef
を返します。
package My::Request;
use parent qw(Plack::Request);
use Mouse::Util::TypeConstraints;
sub typed_param {
my ($self, $key, $type) = @_;
# リクエストパラメータがなければundefを返す
my $val = $self->parameters->get($key) // return undef;
# パラメータが制約を満たすならそのパラメータを、
# 満たさないならundefを返す
return find_type_constraint($type)->check($val) ?
$val : undef;
}
このtyped_
とリスト3で定義したBlog::Status
型を利用すれば、undef
を受け取れます。undef
が返ってきている場合は不正なリクエストであると判断できるため、typed_
メソッドの利用例は次のとおりです。
my $req = My::Request->new($env);
my $status = $req->typed_param( ┓
'blog_status', 'Blog::Status', ┣(1)
); ┛
if (! defined $status) {
# Bad Requestなどを返す
}
blog_
を第1引数に、Blog::Status
を第2引数に渡します。Blog::Status
型がpublic
かprivate
しか受け付けないため、evil
という値を送信してきたら、$status
にはundef
が代入されます。undef
の場合にBad Request
を返すなどの処理をすれば、
動的な型制約を導入してみて
筆者は、
メリット
動的な型制約の導入によって感じたメリットは以下の3つです。
関数の使い方の間違いで不整合が起こらない
最初に紹介したとおり、
長くアプリケーションを運用していると、
引数に型制約をかければ、
関数に何を渡せばよいかが明確になる
関数の引数に何を渡せばよいかわかりやすくなるメリットも感じました。
動的な型制約を導入していない状態では、
Smart::Argsで型制約をかけておけば、
誰が書いても厳しくチェックされる
Smart::Argsを導入する前も、
Smart::Argsは、
デメリット
型制約を導入するデメリットはほとんどありませんでした。1つだけ挙げるとすると、
呼び出しの回数が多いと遅くなる
静的言語での型チェックはコンパイル時に行われ、
Smart::ArgsやMouseの型制約のしくみは、
ベンチマークのコードはリスト5で、
Rate args no_args
args 80645/s -- -98%
no_args 3333333/s 4033% --
use Smart::Args;
use Benchmark qw(cmpthese);
sub no_args_func {
my ($num1, $num2, $num3, $num4) = @_;
return $num1 + $num2 + $num3 + $num4;
}
sub args_func {
args_pos my $num1 => 'Num',
my $num2 => 'Num',
my $num3 => 'Num',
my $num4 => 'Num';
return $num1 + $num2 + $num3 + $num4;
}
cmpthese(100000, {
no_args => sub {
no_args_func(100, 200, 300, 400);
},
args => sub {
args_func(100, 200, 300, 400);
},
});
チェックをしていないno_
は変数への代入と加算しかしていないので当然速く、args_
のようにNum
型だけを使ったチェックを入れるだけで、
秒間数万回実行できるのは十分速いですが、
まとめ
本稿では、
さて、
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT