What is the best way to create trigger accessors with default values ​​in Moose?

I have a situation where I would like to cache some calculations for use later. Let's say I have a list of valid values. Since I'm going to check to see if anything is found on this list, I want it to be a hash for efficiency and convenience. Otherwise, I would have to grep.

If I use Moose, it would be nice if the cache was recalculated every time the list of acceptable values ​​changes. I can do this using a trigger light enough ...

has allowed_values => ( is => 'rw', isa => 'ArrayRef', trigger => sub { my %hash = map { $_ => 1 } @{$_[1]}; $_[0]->allowed_values_cache(\%hash); } ); has allowed_values_cache => ( is => 'rw', isa => 'HashRef', ); 

And both will sync ...

 $obj->allowed_values([qw(up down left right)]); print keys %{ $obj->allowed_values_cache }; # up down left right 

Now let me say that I want the default for allowed_values , a fairly simple change ...

 has allowed_values => ( is => 'rw', isa => 'ArrayRef', trigger => sub { my %hash = map { $_ => 1 } @{$_[1]}; $_[0]->allowed_values_cache(\%hash); }, default => sub { return [qw(this that whatever)] }, ); 

... except that the default installation does not trigger a trigger. To get to DWIM I need to duplicate caching.

 has allowed_values => ( is => 'rw', isa => 'ArrayRef', trigger => sub { $_[0]->cache_allowed_values($_[1]); }, default => sub { my $default = [qw(this that whatever)]; $_[0]->cache_allowed_values($default); return $default; }, ); sub cache_allowed_values { my $self = shift; my $values = shift; my %hash = map { $_ => 1 } @$values; $self->allowed_values_cache(\%hash); return; } 

Moose docs explicitly indicate trigger , which are not called when the default value is set, but it interferes. I do not like duplication there.

Is there a better way to do this?

+7
caching perl moose
source share
2 answers

I recently ran into this, and after a request on the #moose channel #moose he was told to process it like this:

Mark cache_allowed_values as lazy_build , << 23> point _build_cache_allowed_values current allowed_values and place the write trigger on allowed_values , which will clear cache_allowed_values .

Thus, regardless of the order in which values ​​are requested or stored, they will always be right with the least amount of work.


Example:

 has cache_allowed_values => (is => 'ro', lazy_build => 1); sub _build_cache_allowed_values { return { map { $_ => 1 } @{shift->allowed_values} }; } has allowed_values => ( is => 'rw', trigger => sub { shift->clear_cache_allowed_values }, default => ..., ); 
+7
source share

I think that you really want allowed_values be a separate data structure with the desired performance and ordering properties. Since this is not like you care about the order, why not:

 has 'allowed_values' => ( traits => ['Hash'], isa => HashRef[Bool], default => sub { +{} }, handles => { _add_allowed_value => 'set', remove_allowed_value => 'delete', value_is_allowed => 'exists', allowed_values => 'keys', }, ); method add_allowed_value(Str $value){ $self->_add_allowed_value( $value, 1 ); } 

In general, anything that does not correspond to a particular class should be implemented elsewhere. Creating arrays has a faster search time - this is not the work of any class that you write, so it must be implemented elsewhere, and this class must use this class. (In the simple case, like the hash above, it might be OK to ignore this rule. But if it were more complicated, you would definitely want to justify it.)

Edit:

If you want the user to think this is a list, how about:

 use MooseX::Types::Moose qw(Bool ArrayRef HashRef); use MooseX::Types -declare => ['ListHash']; subtype ListHash, as HashRef[Bool]; coerce ListHash, from ArrayRef, via { +{ map { $_ => 1 } @$_ } }; has 'allowed_values' => ( # <same as above> isa => ListHash, writer => 'set_allowed_values', coerce => 1, ); 

Now you can set allowed_values as:

 my $instance = Class->new( allowed_values => [qw/foo bar/] ); $instance->set_allowed_values([qw/foo bar baz/]); 

And refer to them as follows:

 my @allowed_values = $instance->allowed_values; ... if $instance->value_is_allowed('foo'); 

And change them:

 $instance->remove_allowed_value('foo'); $instance->add_allowed_value('gorch'); 

This hides any underlying implementation data from the user.

By the way, does it actually build a hash and use it much faster than a linear scan of 3 elements?

+2
source share

All Articles