2012-05-10 34 views
5

Z Moose, można mieć lazybuilders atrybutów, gdzie konstruktor jest wywoływana, gdy atrybut jest pierwszy dostępny jeżeli atrybut nie została już wypełniona. Możesz mieć typ przymusu atrybutu z coerce, ale jest on stosowany , gdy atrybut jest ustawiony, więc nawet przy inicjalizacji obiektu.Lazy Atrybut Przymus

szukam sposobu realizacji leniwe przymus, gdzie atrybut może być początkowo zaludniony, ale przymusowej tylko wtedy, gdy po raz pierwszy dostępne. Jest to ważne, gdy przymus jest drogi.

W poniższym przykładzie, używam Union Typ i sposób modyfikatory, aby to zrobić:

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

Jednakże istnieje kilka problemów z tym:

  1. lubię typ związkową + modyfikator metody podejście. Jest sprzeczne z sugestią "najlepszych praktyk" do use coercion instead of unions. To nie jest deklaratywne.

  2. muszę to zrobić z wielu atrybutów całej wielu klas. Dlatego potrzebna jest pewna forma suszenia. Mogą to być role atrybutów meta-atrybutów, typ-koercja, co ty.

Aktualizacja: Śledziłem ikegami's sugestię do hermetyzacji drogiego typu przymus wewnątrz obiektu i zapewnić zewnętrzny przymus do tego obiektu:

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; 

To działa, jeśli $foo->x->value nazywa. Nie rozwiązuje to jednak punktu nr 2, ponieważ musiałbym utworzyć podtyp My::ArrayFromInt i ::Lazy dla każdego atrybutu, który chciałbym przekształcić. A jeśli to możliwe, chciałbym unikać wywoływania $foo->x->value.

+1

Jeśli istnieją dwa sposoby reprezentacji układu odniesienia, powinno być możliwe uzyskanie reprezentacji. Przymocuj do obiektu, a następnie pobierz dane z obiektu w wybranym formacie. [Przykład] (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 Problem polega na tym, że przymus jest drogi; Chcę go wykonać tylko wtedy, gdy żądany jest atrybut. – devoid

Odpowiedz

0

Jak posiadające typedef wzdłuż linii opisanych, a następnie robi

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

Byłoby sensu owijać się, że w jakiś sposób pomocnika stworzyć parę obiektów wzdłuż linii

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

, które wykonałoby introspekcję w TargetType, aby uzyskać listę typów prawnych dla niezamkniętego atrybutu shadow i wygenerować parę atrybutów dla ciebie.