2012-05-10 7 views
5

Mit Moose, können Sie haben lazybuilders 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:

  1. 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.

  2. 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.

+1

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

+0

s/'map {1} 1 .. $ val' /' (1) x $ val'/ – ikegami

+0

@ikegami Das Problem ist, dass Zwang teuer ist; Ich möchte es nur ausführen, wenn das Attribut angefordert wird. – devoid

Antwort

0

Wie über die typedef entlang der Linien beschrieben hat, dann

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

tun Es wäre sinnvoll, dass in eine Art Hilfsmethode einpacken das Paar von Objekten entlang der Linien von

zu erstellen

Introspect auf TargetType, um eine Liste der zulässigen Typen für das uncourced Schattenattribut zu erhalten und das Paar der Attribute für Sie zu generieren.