5

Mooseを使用すると、属性がまだ設定されていない場合lazy buildersに、属性が最初にアクセスされたときにビルダーが呼び出される属性を持つことができます。を使用して属性の型強制を行うことができますが、これは属性が設定されるたびに適用されるため、オブジェクトの初期化でも適用されます。coerce

属性が最初に入力される可能性がありますが、最初にアクセスされたときにのみ強制される、遅延強制を実装する方法を探しています。これは、強制が高価な場合に重要です。

次の例では、ユニオン型とメソッド修飾子を使用してこれを行います。

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";

ただし、これにはいくつかの問題があります。

  1. ユニオン型+メソッド修飾子のアプローチは嫌いです。union の代わりに強制を使用するという「ベスト プラクティス」の提案に反します。それは宣言的ではありません。

  2. 多くのクラスにまたがる多くの属性でこれを行う必要があります。したがって、何らかの形式の DRY が必要です。これは、メタ属性の役割、型強制、何を持っている可能性があります。

更新:池上の提案 に従い、高価な型強制をオブジェクト内にカプセル化し、このオブジェクトに外部強制を提供しました。

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のサブタイプを作成する必要があるため、これはポイント 2 を解決しません。そして、できれば::Lazy電話は避けたいです。$foo->x->value

4

1 に答える 1

0

How about having the typedef along the lines described, then doing

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 },
);

It'd make sense to wrap that up into some kind of helper method to create the pair of objects along the lines of

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

which would introspect on TargetType to get a list of legal types for the uncoerced shadow attribute and generate the pair of attributes for you.

于 2012-06-06T10:12:56.463 に答える