Moose: Expiring cached results of calculations whe

2019-04-08 11:52发布

In our classes we have a pattern where we create an attribute to represent a calculated value. For obvious reasons we want to cache the calculated value and then invalidate the cache when one of the underlying values change.

So we currently have this:

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

This long hand method is getting very tedious and error prone when calculated values depend on other calculated values.

Is there a smarter/simpler way for 'bar' to monitor the attributes it depends on vs having 'foo' know who is dependent on it? Also how can I avoid setting bar via hash member access?

标签: perl moose
4条回答
Root(大扎)
2楼-- · 2019-04-08 12:09

Would this work?

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

Output:

$ perl test.pl
update b
update c
查看更多
叼着烟拽天下
3楼-- · 2019-04-08 12:26

If I understand you correctly, you can use triggers to clear attributes when one is set. Here's an example:

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

So, any writes to foo via $obj->foo($newvalue) will cause bar to be cleared, and recreated on next access.

查看更多
一夜七次
4楼-- · 2019-04-08 12:36

I think it is quite possible that you're making this harder on yourself by using an Attributes implicit memoization with lazy, when you could just make the memoization explicit making your whole program more transparent

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's Memoize for information and control of the internal cache, also remember that a Memoized function can not be dependent on the state of the object. So the arguments must be passed in explicitly.

查看更多
干净又极端
5楼-- · 2019-04-08 12:36

I haven't done any poking around in Moose internals and the meta object protocol, but I think this is a good time to do it.

You want to patch the code generation so that when you specify an attribute as

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

the code generation phase creates code for the foo and bar attributes as you specified above.

How to do this is an exercise left to the reader. If I had a clue, I'd try to give you a start. Unfortunately, all I can advise you with is "This is a job for the MOP".

查看更多
登录 后发表回答