2009-11-21 4 views
5

In unseren Klassen haben wir ein Muster, wo wir ein Attribut erstellen, um einen berechneten Wert darzustellen. Aus offensichtlichen Gründen möchten wir den berechneten Wert zwischen zwischenspeichern und dann den Cache ungültig machen, wenn sich einer der zugrunde liegenden Werte ändert.Moose: Abgelaufene Ergebnisse von Berechnungen im Cache, wenn sich Attributwerte ändern?

So haben wir zur Zeit die:

package FooBar; 
use Moose; 

has 'foo' => (
     accessor => { 
      'foo' => sub { 
       my $self = shift; 
       if (@_ > 0) { 
        # writer 
        $self->{foo} = $_[0]; 

     # reset fields that are dependant on me 
     $self->{bar} = undef; 
       } 
       # reader part; 
       return $self->{foo}; 
      } 
     } 
    ); 

has 'bar' => (
     accessor => { 
      'bar' => sub { 
       my $self = shift; 
       if (@_ > 0) { 
        # writer 
        $self->{bar} = $_[0]; 
       } 
       # reader part; 
       $self->{bar} = calculate_bar($self->foo, $self->baz) 
         if (not defined($self->{bar})); 
       return $self->{bar}; 
      } 
     } 
    ); 

sub calculate_bar { ... } 

Diese lange Hand-Methode ist immer sehr mühsam und fehleranfällig, wenn Werte berechnet auf anderen berechneten Werten abhängen.

Gibt es eine klügere/einfachere Möglichkeit für 'bar', die Attribute zu überwachen, die davon abhängen, vs 'foo' wissen, wer ist davon abhängig? Wie kann ich auch vermeiden, Bar über Hash Mitglied Zugriff einstellen?

Antwort

5

Ich denke, es ist durchaus möglich, dass Sie auf dich selbst diesen härter machen durch eine Attribut implizite memoization mit faul verwenden, wenn Sie nur die memoization explizit machen könnten machen Ihre ganzes Programm transparenter

has [qw/foo bar baz/] => (isa => 'Value', is => 'rw'); 

use Memoize; 
memoize('_memoize_this'); 

sub old_lazy_attr { 
    my $self = shift; 
    _memoize_this($self->attr1, $self->attr2, $self->attr3); 
} 

sub _memoize_this { 
    my @args = @_; 
    # complex stuff 
    return $result 
} 

See cpan die Memoize für Informationen und die Steuerung des internen Cache, auch daran denken, dass ein memoized Funktion kann nicht vom Zustand des Objekts abhängig sein. Daher müssen die Argumente explizit übergeben werden.

+0

Hm. Ich habe Probleme damit, mit Memoize Objektdaten zwischenzuspeichern. Was passiert, wenn jede Instanz dieser Klasse unterschiedliche Werte hat? Memoize speichert sie für immer, ungeachtet der Tatsache, dass sie nicht länger nützlich sind, wenn das Objekt zerstört wird, oder? Das bedeutet in einer persistenten App (und das ist wirklich der einzig sinnvolle Ort, um Moose zu benutzen), dass Sie möglicherweise einen riesigen, nutzlosen Cache aufbauen. Nein? Natürlich können Sie manuell Dinge ausrangieren (denke ich!), Aber das ist viel mehr Komplexität über das Elch/faul Beispiel oben, für wenig Gewinn .. – Dan

+1

Ich stimme grundsätzlich nicht zu, nicht nur ist/weniger/komplex und transparenter, aber die Geschwindigkeit und der Gewinn sind vorhersehbar und die Logik ist dort, wo sie sein sollte, anstatt in andere Accessoren gehackt zu werden. Alles, was Sie tun müssen, ist die Unterklasse Memoize :: Expire und setzen Sie den STORE-Sub, um den Cache zu löschen, bevor Sie in den Hash schreiben. –

+1

Ich habe dies als Antwort gewählt, da es den Code drastisch vereinfacht, was ich wirklich anstrebte. Die Tatsache, dass das Ergebnis der Berechnung nicht im Objekt selbst gespeichert ist, ist kein Problem für meine aktuelle Implementierung. Danke EvanCaroll. – clscott

11

Wenn ich Sie richtig verstanden habe, können Sie triggers verwenden, um Attribute zu löschen, wenn eine gesetzt ist. Hier ein Beispiel:

has 'foo' => (
    is  => 'rw', 
    trigger => sub{ 
     my ($self) = @_; 
     $self->clear_bar; 
    } 
); 

has 'bar' => (
    is  => 'rw', 
    clearer => 'clear_bar', 
    lazy => 1, 
    default => sub{ 
     my ($self) = @_; 
     return calculate_bar(...); 
    } 
); 

Also, alle Schreibvorgänge auf foo über $obj->foo($newvalue) verursacht bar gelöscht werden, und beim nächsten Zugriff neu erstellt.

+1

Ich habe gewählt, weil dies die Moose Art ist, die am wenigsten Voodoo schreibt, aber ich denke meine Lösung ist massiv besser. Ich möchte auch die offenkundige Negativseite darauf hinweisen, dass Ihre Arbeitslast mit jeder Attributänderung steigt, da jede Attributänderung das faule (berechnete) Attribut explizit löschen muss. Wenn er 5 Attribute hat, ändert er jedes 5 mal, bevor er das faule Attribut aufruft, das 24 verschwendete Aufrufe ist, um das faule (berechnete) Attribut zu löschen. Dies ist auch missbraucht faul, um den Vorteil der Memoisierung zu bekommen. –

+2

Es ist auch rückwärts von einer Wartbarkeitsperspektive; bar-depends-on-foo ist logischerweise eine Eigenschaft von bar, not foo – ysth

+0

Das ist ein Moose-y-Weg und eine großartige Antwort, aber ich war am glücklichsten, irgendeinen zusätzlichen Elch-basierten Code auszusortieren, der der Geschäftslogik gerade in die Quere kam . Danke Dan. – clscott

0

Würde das funktionieren?

#!/usr/bin/perl 

package Test; 

use Modern::Perl; 
use Moose; 

has a => (is => 'rw', isa => 'Str', trigger => \&change_a); 
has b => (is => 'rw', isa => 'Str', trigger => \&change_b); 
has c => (is => 'rw', isa => 'Str'); 

sub change_a 
{ 
    my $self = shift; 
    say 'update b'; 
    $self->b($self->a . ', bar'); 
} 

sub change_b 
{ 
    my $self = shift; 
    say 'update c'; 
} 

package main; 

my $test = Test->new->a('Foo'); 

Ausgang:

$ perl test.pl 
update b 
update c 
+1

Ich bin mir nicht sicher, warum du denkst, er will b setzen, von a. aber das ist eine Katastrophe von kreisförmigen Auslösern in der Herstellung. Da Sie ihn über die von Else bereitgestellten öffentlichen Schnittstellen statt über eine zufällige Änderung von b durch den Setter setzen, wird sein Trigger auf einen Wert gesetzt, der den Auslöser auslöst. Attribute über Trigger setzen, die die öffentliche Schnittstelle verwenden ist eine schlechte Idee. –

+0

Das Setzen von '$ b' aus' $ a' war eine Art zu sagen, dass er den berechneten Wert ('$ b') aktualisieren kann, wenn sich einer der Masterwerte (' $ a') ändert. Und ich glaube nicht, dass es einen Triggerzyklus geben wird, wenn er einfach die berechneten Eigenschaften aktualisieren will. Es könnte sein, dass ich deine Argumentation einfach nicht verstehe - ein Beispiel? – zoul

+2

(Aber sicherlich ist diese Lösung schlechter als die oben genannten, weil es $ $ b nicht träge berechnet.) – zoul

0

Ich habe in den Moose-Interna und dem Meta-Objekt-Protokoll nicht herumgestochert, aber ich denke, das ist ein guter Zeitpunkt, es zu tun.

Sie möchten die Codegenerierung patchen, so dass, wenn Sie ein Attribut als

has 'foo' =>(); 
has 'bar' => ( 
    depends_on => [qw(foo)], 
    lazy => \&calculate_bar, 
); 

der Phasencodegenerierung angeben erzeugt Code für die foo und bar Attribute wie Sie oben angegeben.

Wie dies zu tun ist eine Übung für den Leser. Wenn ich eine Ahnung hätte, würde ich versuchen, dir einen Anfang zu geben. Leider kann ich Ihnen nur empfehlen: "Dies ist ein Job für das MOP".

+0

Das ist viel mehr Arbeit als die Antwort, die ich angenommen habe. Es scheint auch unpraktisch zu sein, da es das Testen und die Wartung eines MooseX :: -Moduls, einer Art Plugin oder eines Patches gegen Moose selbst erfordert, die niemals in den Core aufgenommen werden. – clscott

+0

Schauen Sie sich die Abschnitte'Erweiterungen' und 'Meta' im Kochbuch an. Es wirkt zunächst unheimlich. Wenn Sie die Dokumente lesen, dann sieht es nicht so schlecht aus. Egal, wie Sie Ihr Problem lösen, die Hauptsache ist, den fiddly Code zu minimieren, den Sie pflegen müssen. Wenn ein Meta-Ansatz dies tut, dann gut. Ansonsten verwende etwas anderes. – daotoad

Verwandte Themen