懒惰的属性强制

时间:2012-05-10 18:05:36

标签: perl moose lazy-evaluation

使用Moose,您可以在属性上使用lazy builders,在首次访问属性时调用构建器如果属性尚未填充。您可以使用coerce对属性进行类型强制,但只要设置了属性,就会应用,因此即使在对象初始化时也是如此。

我正在寻找一种实现 lazy coercion 的方法,其中一个属性最初可能会被填充,但只有在首次访问时才被强制执行。当胁迫很昂贵时,这很重要。

在下面的示例中,我使用联合类型和方法修饰符来执行此操作:

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. 我不喜欢联合类型 + 方法修饰符方法。这违反了"最佳实践"建议use coercion instead of unions。它不是声明性的。

  2. 我需要在许多类中使用许多属性来执行此操作。因此需要某种形式的DRY。这可能是元属性角色,类型强制,你有什么。

  3. 更新 我遵循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,则此方法有效。但是,这并不能解决第2点,因为我需要为每个要转换的属性创建My::ArrayFromInt::Lazy子类型。我希望尽可能避免致电$foo->x->value

1 个答案:

答案 0 :(得分:0)

如何在所描述的行中使用typedef,然后执行

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

将它包装成某种辅助方法来创建一对对象

是有意义的。
has_lazily_coerced x => (
    is => 'ro',
    isa => 'TargetType',
);

会反省TargetType以获取未强制阴影属性的合法类型列表,并为您生成一对属性。