2009-11-21 11 views
5

クラスでは、 の計算値を表す属性を作成するパターンがあります。明白な理由から、計算された値 をキャッシュし、基になる値の1つが変更されたときにキャッシュを無効にする必要があります。Moose:属性値が変更されたときのキャッシュされた計算結果の期限切れ?

だから我々は現在、この持っている:他の計算値に依存 値を計算した場合、この長い手の方法は非常に退屈でエラーが発生しやすくなっている

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 { ... } 

を。

'bar'がそれに依存する属性を監視するためのよりスマートでシンプルな方法はありますか? 対して誰がそれに依存しているのかを知っていますか?また、どのようにハッシュ メンバーアクセスを介してバーを設定するのを避けることができますか?

答えて

5

私はあなたがあなただけのメモ化明示的にすること作ることができたときに、属性、怠惰との暗黙のメモ化を使用することによって、この困難を自分で作っているということは十分に可能だと思うあなたプログラム全体より透明

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 
} 

参照CPANのMemoize内部キャッシュの情報と制御のために、また、メモ化することを覚えておいてください関数はオブジェクトの状態に依存することはできません。したがって、引数は明示的にに渡されなければなりません。

+0

Hm。 Memoizeを使用してオブジェクトデータをキャッシュするときに問題が発生します。このクラスのすべてのインスタンスが異なる値を持つ場合はどうなりますか? Memoizeは、オブジェクトが破棄されたときにもはやそれらが役に立たないという事実にかかわらず、それらを永久にキャッシュしますか?つまり、永続的なアプリケーションでは(実際にはMooseを使用する唯一の賢明な場所です)、巨大で無駄なキャッシュを潜在的に増やすことになります。いいえ? もちろん、手動で期限切れのものを混乱させることもできます(私は思っています)。しかし、これは上記のムース/怠け者の例よりも複雑です。 – Dan

+1

私は根本的にそれだけではなく、透明性は向上しますが、スピード・ヒットとゲインは予測可能であり、ロジックは他のアクセッサにハッキングされるべきではありません。 Memoize :: Expireをサブクラス化し、ハッシュに書き込む前にキャッシュをクリアするようにSTOREサブを設定するだけです。 –

+1

コードを大幅に簡素化するので、これを答えとして選択しました。これは私が本当に努力していたものでした。 計算結果がオブジェクト自体に格納されていないことは、現在の実装では問題ではありません。 ありがとうEvanCaroll。 – clscott

11

私が正しく理解していれば、triggersを使用して、属性が設定されているときに属性をクリアすることができます。

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(...); 
    } 
); 

ので、$obj->foo($newvalue)経由fooへの書き込みがbarがクリアされ、次のアクセスに再作成されるようになります:ここでは例です。

+1

私はこれを投票しました。これは最低のブードーを書く必要があるためですが、私の解決策は非常に優れていると思います。私はまた、これに明らかな欠点を指摘したいと思っていました。各属性の変更は、レイジー(計算された)属性を明示的にクリアする必要があるため、ワークロードは各属性の変更に伴い増加します。彼が5つの属性を持っていて、遅延のある属性を呼び出す前にそれぞれを5回変更すると、24の無駄な呼び出しが遅延(計算された)属性をクリアします。これはまた、メモ帳の利益を得るために怠惰を悪用しています。 –

+2

メンテナンス性の観点からも逆です。 bar-depends-on-fooは論理的にbarのプロパティであり、fooではない – ysth

+0

これはムース・イ・ウェイですばらしい答えですが、私はビジネス・ロジックの方法でちょうど得られた余分なムースベースのコードを削除するのが幸いでした。 Danさんありがとうございます。 – clscott

0

これは機能しますか?

#!/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'); 

出力:

$ perl test.pl 
update b 
update c 
+1

私はなぜあなたが彼をbから設定したいと思うか分かりません。しかし、これは、作成における循環トリガの災害です。あなたがメタではなくムース提供のパブリックインターフェイスを通して設定しているので、セッターを介してbの偶発的な変更が発生すると、そのトリガはaに設定され、トリガを引き起こします。 パブリックインターフェイス悪い考えです。 –

+0

'$ b'を' $ a'から設定することは、マスター値( '$ a')の1つが変更されたときに計算値(' $ b')を更新できるということでした。そして、計算されたプロパティを単に更新したいと思えば、トリガーサイクルはないと思う。私は単にあなたの議論を得ていないかもしれません - 例がありますか? – zoul

+2

(確かに、この解決法は上記のものよりも悪いですが、 '$ b'を遅く再計算しません) – zoul

0

私はMooseの内部構造とメタオブジェクトプロトコルで何かを掘り下げていませんが、これは良いタイミングだと思います。

はあなたが

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

あなたは上記の指定されたコード生成相はfoobar属性のコードを作成して属性を指定するときようにコード生成にパッチを適用します。

これを行う方法は、読者に残された課題です。手がかりがあれば、私はあなたにスタートを与えようとします。残念ながら、私があなたにアドバイスできるのは、 "これはMOPの仕事です"です。

+0

これは私が受け入れた答えよりも多くの仕事です。また、MooseX ::モジュールのテストとメンテナンス、プラグインのいくつかのタイプ、またはコアに受け入れられないMoose自体に対するパッチを必要とするため、実用的ではないようです。 – clscott

+0

Cookbookの 'Extending'セクションと' Meta'セクションを調べてください。最初は恐ろしいようです。あなたがドキュメントを読むと、それほど悪くはありません。どのように問題を解決しても、主なものは保守しなければならない不正なコードを最小限に抑えることです。メタアプローチがこれを行うならば、良い。それ以外の場合は、別のものを使用します。 – daotoad

関連する問題