Con Moose, si può avere lazy
builders
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:
non mi piace il tipo unione + metodo di modifica approccio. Va contro il suggerimento "Best Practices" a use coercion instead of unions. Non è dichiarativo.
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.
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
s/'mappa {1} 1 .. $ val' /' (1) x $ val'/ – ikegami
@ikegami Il problema è che la coercizione è costosa; Voglio solo eseguirlo se l'attributo è stato chiesto. – devoid