=encoding utf8 =pod =head1 題名 Moose::Cookbook::Basics::Recipe5 - サブタイプふたたび、Bクラスの型変換 =head1 概要 package Request; use Moose; use Moose::Util::TypeConstraints; use HTTP::Headers (); use Params::Coerce (); use URI (); subtype 'My::Types::HTTP::Headers' => as class_type('HTTP::Headers'); coerce 'My::Types::HTTP::Headers' => from 'ArrayRef' => via { HTTP::Headers->new( @{$_} ) } => from 'HashRef' => via { HTTP::Headers->new( %{$_} ) }; subtype 'My::Types::URI' => as class_type('URI'); coerce 'My::Types::URI' => from 'Object' => via { $_->isa('URI') ? $_ : Params::Coerce::coerce( 'URI', $_ ); } => from 'Str' => via { URI->new( $_, 'http' ) }; subtype 'Protocol' => as 'Str' => where { /^HTTP\/[0-9]\.[0-9]$/ }; has 'base' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 ); has 'uri' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 ); has 'method' => ( is => 'rw', isa => 'Str' ); has 'protocol' => ( is => 'rw', isa => 'Protocol' ); has 'headers' => ( is => 'rw', isa => 'My::Types::HTTP::Headers', coerce => 1, default => sub { HTTP::Headers->new } ); =head1 本文 このレシピではCというシュガー関数で定義される型変換を紹介します。型変換は、既存の型制約と組み合わせて、ある型から別の型への(一方的な)変換を定義するものです。 これは非常に強力な機能ですが、魔術的な機能でもあるので、アトリビュートを型変換する際には明示的に、Cというアトリビュートオプションを真に設定しなければなりません。 まずはほかの型から型変換するサブタイプを作ります。 subtype 'My::Types::HTTP::Headers' => as class_type('HTTP::Headers'); ここではCを直接型として利用するのではなく、サブタイプを作っています。これは、型変換はグローバルなものであるため、CクラスでCの型変換を定義すると、同じPerlインタプリタ上でMooseを使っている「すべての」クラスで同じ型変換が定義されてしまうためです。このような名前空間の汚染は避けるのがL<ベストプラクティス|Moose::Manual::BestPractices>です。 Cというシュガー関数は単にこれのショートカットです。 subtype 'HTTP::Headers' => as 'Object' => where { $_->isa('HTTP::Headers') }; Mooseを使っているクラスはすべて内部的に型制約が生成されますが、Mooseを使っていないクラスについては明示的に型を宣言しなければなりません。 型を宣言しておくと、この先この新しい型を直接使えるようになります。 has 'headers' => ( is => 'rw', isa => 'HTTP::Headers', default => sub { HTTP::Headers->new } ); これで、空のLインスタンスがデフォルトの簡単なアトリビュートが生成されます。 Lのコンストラクタは、HTTPヘッダのフィールドに対応するキーと値の組のリストを受け付けます。Perlでは、このようなリストは配列リファレンスないしハッシュリファレンスに保存できますが、このCアトリビュートでも、Bインスタンスのかわりにそういったデータ構造を受け付けて適切な処理をさせたいところ。こういうときこそ型変換の出番です。 coerce 'My::Types::HTTP::Headers' => from 'ArrayRef' => via { HTTP::Headers->new( @{$_} ) } => from 'HashRef' => via { HTTP::Headers->new( %{$_} ) }; Cの最初の引数は、変換「先」の型です。そのあとには、C節とC節の組を続けます。C関数は別の型の名前を、Cは実際に型変換を行うサブルーチンリファレンスを取ります。 ただし、型変換を定義しても、Mooseに型変換したいアトリビュートを伝えないと何も起こりません。 has 'headers' => ( is => 'rw', isa => 'My::Types::HTTP::Headers', coerce => 1, default => sub { HTTP::Headers->new } ); これで、Cを初期化するときにCやCを使うと、新しいLインスタンスに変換されます。この型変換を用意すると、以下のコードはすべて同じ結果になります。 $foo->headers( HTTP::Headers->new( bar => 1, baz => 2 ) ); $foo->headers( [ 'bar', 1, 'baz', 2 ] ); $foo->headers( { bar => 1, baz => 2 } ); ご覧の通り、型変換は気をつけて使うと型制約のチェックによる「安全性」を保ったままクラスのインタフェースを非常にオープンなものにできます。(1) 次の型変換では、既存のCPANモジュールを使って型変換を実装する方法を紹介します(ここではLを使います)。 LクラスはMooseを使っていないので、今回もクラス型を宣言しておく必要があります。 subtype 'My::Types::URI' => as class_type('URI'); 続いて型変換を定義します。 coerce 'My::Types::URI' => from 'Object' => via { $_->isa('URI') ? $_ : Params::Coerce::coerce( 'URI', $_ ); } => from 'Str' => via { URI->new( $_, 'http' ) }; 最初の型変換は、何らかのオブジェクトを受け取ってCオブジェクトに変換するものです。型変換システムはそれほど賢いものではありませんので、オブジェクトがもともとLかどうかはチェックしません。そこで、そのチェックは自分でして、LでなければLに魔法をかけてもらい、その返り値を流用しています。 もし(なんらかの事情で)LがLオブジェクトを返さなかった場合は、型制約のエラーが発生します。 もうひとつの型変換は、文字列を受け取ってLに変換するものです。この場合は、型変換を利用してデフォルトの振る舞いを適用しています(文字列はCのURIであると仮定しています)。 最後に、アトリビュートの型変換を忘れずに有効にしておく必要があります。 has 'base' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 ); has 'uri' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 ); 型変換を再利用すると、複数のアトリビュートで首尾一貫したAPIを強制することができます。 =head1 まとめ このレシピでは、型変換を利用してより柔軟で物わかりのよいAPIを生成する方法を紹介しました。ただし、強力な魔法を使うときはいつでもそうですが、多少用心することをおすすめします。場合によっては値をどう処理するのがよいか推測するより、拒否してしまう方がよい場合もあるのですから。 また、新しいCのサブタイプを定義するショートカットとして、Cというシュガー関数を使う方法も紹介しました。 =head1 脚注 =over 4 =item (1) この例に限ってはもっと安全にできます。本当は要素の数が偶数の配列のみ型変換したいのですから、新たにCという型を用意して、プレーンなCではなく、その型から変換するようにすればよいのです。 =back =head1 作者 Stevan Little Estevan@iinteractive.comE Dave Rolsky Eautarch@urth.orgE =head1 COPYRIGHT AND LICENSE Copyright 2006-2009 by Infinity Interactive, Inc. L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut