2012-05-10 60 views
5

隨着Moose,你可以有lazybuilders的屬性,其中生成器調用時該屬性是第一次訪問如果屬性是不是已經填充。您可以使用coerce對屬性進行類型強制轉換,但只要屬性設置爲,就會應用,因此即使在對象初始化時也如此。lazy屬性強制

我正在尋找一種方法來實現懶惰強制,其中一個屬性可能最初被填充,但只有當它被第一次訪問時才被強制。強制價格昂貴時,這一點很重要。

在下面的例子中,我使用的聯合類型和方法修飾符來做到這一點:

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。這可能是元屬性角色,類型強制,你有什麼。

更新: 我跟着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子類型。如果可能的話,我想避免撥打$foo->x->value

+1

如果有兩種表示數據的方式,一個應該能夠得到任何表示。強迫進入對象,然後以所需格式從對象中獲取數據。 [實施例](http://stackoverflow.com/questions/10506416/can-i-use-an-attribute-modifer-in-moose-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 }, 
); 

它會意義來包裝成某種形式的輔助方法來創建沿着

的線對對象
has_lazily_coerced x => (
    is => 'ro', 
    isa => 'TargetType', 
); 

它會在TargetType上反思以獲得非強制陰影屬性的合法類型列表併爲您生成一對屬性。