【发布时间】:2012-05-19 08:46:49
【问题描述】:
使用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";
但是这样做有一些问题:
我不喜欢 联合类型 + 方法修饰符 的方法。它违背了use coercion instead of unions 的“最佳实践”建议。它不是声明性的。
我需要使用 many 类中的 many 属性来执行此操作。因此需要某种形式的 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,这将起作用。但是,这并不能解决第 2 点,因为我需要为要转换的每个属性创建 My::ArrayFromInt 和 ::Lazy 子类型。如果可能的话,我想避免打电话给$foo->x->value。
【问题讨论】:
-
如果有两种表示基准的方式,应该能够得到任何一种表示。强制转换为对象,然后以您想要的格式从对象中获取数据。 Example
-
s/
map { 1 } 1..$val/(1) x $val/ -
@ikegami 问题是强制很昂贵;我只想在请求属性时执行它。
-
这就是为什么我说要强制一个对象(有一个执行强制的 getter)而不是进行昂贵的强制。
标签: perl moose lazy-evaluation