Con Moose, que puede tener lazy
builders
en atributos, donde se llama al constructor cuando el atributo es el primer acceder si el atributo no estaba ya poblada. Puede tener la coerción de tipo de un atributo con coerce
, pero esto se aplica cada vez que se establece el atributo, por lo que incluso en la inicialización de objetos.Lazy Atributo coerción
Estoy buscando una forma de implementar coerción lenta, donde un atributo puede ser inicialmente poblado, pero solo se coacciona cuando se accede por primera vez. Esto es importante cuando la coerción es costosa.
En el siguiente ejemplo, que utilizan un tipo unión y método modificadores de hacer esto:
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";
Sin embargo, hay algunos problemas con este:
No me gusta el tipo Unión + modificador de método enfoque. Va en contra de la sugerencia de "Mejores prácticas" al use coercion instead of unions. No es declarativo.
tengo que hacer esto con muchos atributos través muchas clases. Por lo tanto, se necesita alguna forma de DRY. Esto podría ser roles de meta-atributo, coerción de tipo, lo que tienes.
Actualización: Seguí ikegami's sugerencia para encapsular la conversión de tipos caros dentro de un objeto y proporcionar una coacción externa a este objeto:
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;
Esto funciona si $foo->x->value
se llama. Sin embargo, esto no resuelve el punto n. ° 2, ya que necesitaría crear My::ArrayFromInt
y el subtipo ::Lazy
para cada atributo que me gustaría transformar. Y me gustaría evitar llamar al $foo->x->value
si es posible.
Si hay dos formas de representar un dato, uno debería poder obtener cualquiera de las representaciones. Coaccione en un objeto, luego busque los datos del objeto en el formato que desee. [Ejemplo] (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/'map {1} 1 .. $ val' /' (1) x $ val'/ – ikegami
@ikegami El problema es que la coacción es costosa; Solo quiero realizarlo si se solicita el atributo. – devoid