懒惰的属性强制

dev*_*oid 5 perl moose lazy-evaluation

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

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

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

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";
Run Code Online (Sandbox Code Playgroud)

但是这有一些问题:

  1. 我不喜欢union类型 + 方法修饰符方法.它反对使用强制而不是工会的"最佳实践"建议.这不是声明性的.

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

更新: 我遵循了ikegami的建议,将昂贵的类型强制封装在一个对象中,并为此对象提供外部强制:

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;
Run Code Online (Sandbox Code Playgroud)

如果$foo->x->value被调用,这是有效的.然而,这并没有解决第2点,因为我需要为我想要转换的每个属性创建My::ArrayFromInt::Lazy子类型.$foo->x->value如果可能的话,我想避免打电话.

Pie*_*ley 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 },
);
Run Code Online (Sandbox Code Playgroud)

将其包装到某种辅助方法中以沿着以下方式创建一对对象是有意义的

has_lazily_coerced x => (
    is => 'ro',
    isa => 'TargetType',
);
Run Code Online (Sandbox Code Playgroud)

它将对 TargetType 进行内省,以获取非强制影子属性的合法类型列表,并为您生成属性对。