Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moose: Expiring cached results of calculations when attribute values change?

Tags:

perl

moose

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?

like image 202
clscott Avatar asked Nov 21 '09 14:11

clscott


2 Answers

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.

like image 135
Dan Avatar answered Sep 22 '22 11:09

Dan


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.

like image 36
NO WAR WITH RUSSIA Avatar answered Sep 20 '22 11:09

NO WAR WITH RUSSIA