Mit Moose, können Sie haben lazy
builders
auf Attribute, wo die Builder werden aufgerufen, wenn das Attribut ersten zugegriffen wird, wenn das Attribut nicht bereits bevölkert war. Sie können eine Typumdrehung eines Attributs mit coerce
haben, dies wird jedoch immer dann auf angewendet, wenn das Attribut gesetzt ist, also auch bei der Objektinitialisierung.Faule Attribut Coercion
Ich bin auf der Suche nach einer Möglichkeit zu implementieren Lazy Coercion, wo ein Attribut anfangs gefüllt werden kann, aber nur beim ersten Zugriff gezwungen wird. Dies ist wichtig, wenn Zwang teuer ist.
Im folgende Beispiel verwende ich einen Union-Typen und Verfahren Modifikatoren, dies zu tun:
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";
jedoch ein paar Probleme mit diesem gibt es:
Ich mag nicht den Union-Typen + Methode Modifikator Ansatz. Es geht gegen den "Best Practices" Vorschlag zu use coercion instead of unions. Es ist nicht deklarativ.
Ich brauche dies mit viele Attribute über viele Klassen zu tun. Daher wird eine Form von DRY benötigt. Dies könnte Meta-Attribut-Rollen, Typ-Zwang, was hast du.
Update: ich ikegami's Vorschlag gefolgt, die teure Art Zwang innerhalb eines Objekts zu kapseln und einen äußeren Zwang zu diesem Objekt bieten:
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;
Dies funktioniert, wenn $foo->x->value
genannt wird. Dies löst jedoch Punkt 2 nicht, da ich My::ArrayFromInt
und den ::Lazy
Subtyp für jedes Attribut erstellen müsste, das ich transformieren möchte. Und ich möchte vermeiden, $foo->x->value
wenn möglich zu nennen.
Wenn es zwei Möglichkeiten gibt, ein Datum zu repräsentieren, sollte man in der Lage sein, beide Darstellungen zu erhalten. Wechseln Sie in ein Objekt und holen Sie die Daten aus dem Objekt in dem gewünschten Format. [Beispiel] (http://stackoverflow.com/questions/10506416/cani-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 Das Problem ist, dass Zwang teuer ist; Ich möchte es nur ausführen, wenn das Attribut angefordert wird. – devoid