2012-05-10 15 views
5

Con Moose, si può avere lazybuilders su attributi, in cui il costruttore viene chiamato quando l'attributo è prima accede se l'attributo non era già popolato. È possibile impostare la coercizione di tipo di un attributo con coerce, ma questo viene applicato ogni volta che l'attributo è impostato su, quindi anche sull'inizializzazione dell'oggetto.pigro attributo coercizione

Sto cercando un modo per implementare coercizione pigra, in cui un attributo può essere inizialmente popolato, ma è forzato solo al primo accesso. Questo è importante quando la coercizione è costosa.

Nel seguente esempio, io uso un tipo di unione e di metodo modificatori per fare questo:

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

Tuttavia ci sono alcuni problemi con questo:

  1. non mi piace il tipo unione + metodo di modifica approccio. Va contro il suggerimento "Best Practices" a use coercion instead of unions. Non è dichiarativo.

  2. ho bisogno di fare questo con molti attributi attraverso molti classi. Pertanto è necessaria una forma di DRY. Questo potrebbe essere il ruolo dei meta-attributi, la coercizione di tipo, cosa hai.

Aggiornamento: Ho seguito ikegami's suggerimento per incapsulare il tipo costoso coercizione all'interno di un oggetto e fornire una coercizione esterna a questo oggetto:

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; 

Questo funziona se $foo->x->value è chiamato. Tuttavia, questo non risolve il punto 2, poiché dovrei creare My::ArrayFromInt e il sottotipo ::Lazy per ogni attributo che vorrei trasformare. E vorrei evitare di chiamare $foo->x->value se possibile.

+1

Se ci sono due modi per rappresentare un dato, si dovrebbe essere in grado di ottenere entrambe le rappresentazioni. Costringere in un oggetto, quindi recuperare i dati dall'oggetto nel formato desiderato. [Esempio] (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/'mappa {1} 1 .. $ val' /' (1) x $ val'/ – ikegami

+0

@ikegami Il problema è che la coercizione è costosa; Voglio solo eseguirlo se l'attributo è stato chiesto. – devoid

risposta

0

ne dite di avere il typedef lungo le linee descritte, poi facendo

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

Sarebbe senso per avvolgere che fino in una sorta di metodo di supporto per creare la coppia di oggetti lungo le linee di

che si introspezza su TargetType per ottenere un elenco di tipi legali per l'attributo shadow non riparato e generare la coppia di attributi per l'utente.