2012-05-10 9 views
5

Mooseを使用すると、属性がすでにを読み込まれなかった場合、ビルダーは、属性があるときに呼び出され、属性、上のlazybuildersが最初にアクセスすることができます。属性の強制型変換はcoerceで行うことができますが、属性がに設定されている場合は常にに適用されるため、オブジェクトの初期化時にも適用されます。レイジー属性強制は

遅延型変換を実装する方法を探していますが、属性は最初に読み込まれますが、最初にアクセスされたときにのみ強制されます。強制が高価な場合は、これが重要です。次の例では

、私はこれを行うには労働組合の種類と方法修飾子を使用します。

  1. 私は組合の種類を嫌い:

    package My::Foo; 
    use Moose; 
    has x => (
        is => 'rw', 
        isa => 'ArrayRef | Int', 
        required => 1 
    ); 
    
    around "x" => sub { 
        my $orig = shift; 
        my $self = shift; 
        my $val = $self->$orig(@_); 
        unless(ref($val)) { 
         # Do the cocerion 
         $val = [ map { 1 } 1..$val ]; 
         sleep(1); # in my case this is expensive 
        } 
        return $val; 
    }; 
    1; 
    
    my $foo = My::Foo->new(x => 4); 
    is_deeply $foo->x, [ 1, 1, 1, 1 ], "x converted from int to array at call time"; 
    

    しかしこれにはいくつかの問題があります + 方法修飾子アプローチ。 use coercion instead of unionsへの「ベストプラクティス」の提案に反する。宣言的ではありません。

  2. 私は多くクラス全体で多く属性でこれを行う必要があります。したがって、何らかのDRYが必要です。これは、メタ属性の役割、タイプ強制、何があります。

アップデート:私はオブジェクト内の高価な型変換をカプセル化し、このオブジェクトに外側強制を提供するために、ikegami'sの提案に従っ

package My::ArrayFromInt; 
use Moose; 
use Moose::Util::TypeConstraints; 
subtype 'My::ArrayFromInt::Inner', 
    as 'ArrayRef[Int]'; 
coerce 'My::ArrayFromInt::Inner', 
    from 'Int', 
    via { return [ (1) x $_ ] }; 
has uncoerced => (is => 'rw', isa => 'Any', required => 1); 
has value => (
    is  => 'rw', 
    isa  => 'My::ArrayFromInt::Inner', 
    builder => '_buildValue', 
    lazy => 1, 
    coerce => 1 
); 
sub _buildValue { 
    my ($self) = @_; 
    return $self->uncoerced; 
} 
1; 
package My::Foo; 
use Moose; 
use Moose::Util::TypeConstraints; 
subtype 'My::ArrayFromInt::Lazy' => as class_type('My::ArrayFromInt'); 
coerce 'My::ArrayFromInt::Lazy', 
    from 'Int', 
    via { My::ArrayFromInt->new(uncoerced => $_) }; 
has x => (
    is => 'rw', 
    isa => 'My::ArrayFromInt::Lazy', 
    required => 1, 
    coerce => 1 
); 
1; 

$foo->x->valueが呼び出された場合、これは動作します。しかし、私はMy::ArrayFromInt::Lazyサブタイプを作成して、変換したい属性ごとにポイント2を解決するわけではありません。可能であれば$foo->x->valueに電話するのは避けたいと思います。

+1

データを表す方法が2つある場合は、いずれかの表現を得ることができるはずです。オブジェクトを強制し、必要な形式でオブジェクトからデータを取得します。 [例](http://stackoverflow.com/questions/10506416/can-i-use-an-attribute-modifer-in-mose-in-a-base-class-to-handle-multiple-attri/10508753# 10508753) – ikegami

+0

s/'map {1} 1 .. $ val' /'(1)x $ val'/ – ikegami

+0

@ikegami問題は強制は高価だということです。私は属性が要求されている場合にのみ実行したい。 – devoid

答えて

0

どう

の線に沿ってオブジェクトのペアを作成するためのヘルパーメソッドのいくつかの種類にそれを包むために、それは意味をなさないと思います

has _x => (
    is  => 'ro', 
    isa  => 'Int|MyArrayOfInts', 
    init_arg => 'x', 
    required => 1, 
); 

has x => (
    is => 'ro', 
    lazy => 1, 
    isa => 'MyArrayOfInts', 
    coerce => 1, 
    default => sub { $_[0]->_x }, 
); 

をやって、説明した線に沿ってのtypedefを持っていることについて

has_lazily_coerced x => (
    is => 'ro', 
    isa => 'TargetType', 
); 

TargetTypeをイントロスペクションして、強制されていないシャドウ属性の有効なタイプのリストを取得し、属性のペアを生成します。