2009-11-21 59 views
5

在我們的課程中,我們有一個模式,我們創建一個屬性來表示一個 計算值。出於顯而易見的原因,我們希望緩存計算值 ,然後在其中一個基礎值發生更改時使緩存無效。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 { ... } 

當計算 依賴於其它計算值值這個長手方法越來越非常繁瑣,而且容易出錯。

有沒有更智能/更簡單的方法來監控它所依賴的屬性 vs'foo'是否知道誰依賴於它?另外我怎樣才能避免通過散列 成員訪問設置欄?

回答

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信息和內部緩存的控制,還記得,Memoized函數不能依賴於對象的狀態。所以參數必須明確地通過

+0

嗯。我在使用Memoize緩存對象數據時遇到了問題。如果這個類的每個實例具有不同的值,會發生什麼? Memoize將永久緩存它們,而不管它們在對象被銷燬時不再有用的事實,對嗎?這意味着在一個持久的應用程序(這真的是唯一明智的地方使用穆斯),你可能會增長一個巨大的,無用的緩存。沒有? 當然,你可以手動(我想!)手動過期的東西亂七八糟,但這比上面的駝鹿/懶惰的例子更復雜,收益不大。 – Dan 2009-11-22 16:32:24

+1

我從根本上不同意,不只是它/更少/複雜和更加透明,但是速度和收益是可以預測的,而邏輯就是它應該被應用而不是被其他訪問者攻擊的地方。所有你需要做的是子類Memoize :: Expire,並設置STORE子清除緩存,然後寫入哈希。 – 2009-11-23 03:06:29

+1

我選擇這個作爲答案,因爲它極大地簡化了代碼,這正是我真正努力的目標。 計算結果未存儲在對象本身中的事實對我當前的實現來說不是問題。 謝謝EvanCaroll。 – clscott 2009-11-23 17:57:29

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個浪費的調用來清除惰性(計算)的屬性。這也是濫用懶惰得到memoization的好處。 – 2009-11-21 20:35:01

+2

從可維護性的角度來看,它也是倒退的; bar-depends-on-foo在邏輯上屬於酒吧的屬性,而不是foo – ysth 2009-11-22 05:19:03

+0

這是一個Moose-y方式和一個很好的答案,但我最高興地刪除了任何額外的基於業務邏輯的基於Moose的代碼。 謝謝丹。 – clscott 2009-11-23 18:00:41

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,從a。但這是造成循環觸發的災難。因爲你通過moose提供的公共接口而不是meta來設置它,通過setter的b中的意外更改將使其觸發器設置爲a,這將導致其觸發器... 使用公共接口通過觸發器設置屬性是一個壞主意。 – 2009-11-21 20:38:35

+0

從'$ a'設置'$ b'是一種說法,當主值('$ a')之一改變時,他可以更新計算值('$ b')。如果他只是想更新計算的屬性,我不認爲會有觸發週期。這可能是我根本沒有得到你的論點 - 有一個例子嗎? – zoul 2009-11-22 11:10:41

+2

(但肯定這個解決方案比上面的更糟,因爲它不會重新計算'$ b'懶惰。) – zoul 2009-11-22 11:14:06

0

我沒有在Moose內部和元對象協議中做任何動作,但我認爲這是一個很好的時機。

你要這樣,當你指定一個屬性作爲

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

代碼生成階段,你上面指定的foobar屬性創建代碼補丁代碼生成。

如何做到這一點是留給讀者的練習。如果我有線索,我會嘗試給你一個開始。不幸的是,我可以建議你的是「這是一份MOP的工作」。

+0

這比我接受的答案要多得多。這似乎也不切實際,因爲它需要測試和維護MooseX ::模塊,某種類型的插件或針對Moose本身永遠不會被接受進入核心的補丁。 – clscott 2009-11-23 18:02:41

+0

查看Cookbook中的「Extending」和「Meta」部分。起初它似乎很可怕。當你閱讀文檔時,它看起來並不那麼糟糕。不管你如何解決你的問題,最主要的是要儘量減少你必須維護的繁瑣代碼。如果元方法做到這一點,那麼很好。否則,使用別的東西。 – daotoad 2009-11-23 20:25:19