7

我有一种情况,我想缓存一些计算供以后使用。假设我有一个允许值列表。由于我将检查该列表中是否有任何内容,因此我希望将其作为哈希值以提高效率和方便性。否则我必须grep。

如果我使用 Moose,如果每次更改允许值列表时都重新计算缓存,那就太好了。我可以用一个足够简单的触发器来做到这一点......

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',
);

并且两者将保持同步...

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

现在假设我想要一个默认的allowed_values,足够简单的改变......

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)]
    },
);

...除了设置默认值不会调用触发器。要将其用于 DWIM,我需要复制缓存。

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 文档明确表示trigger在设置默认值时不会被调用,但它会妨碍您。我不喜欢那里的重复。

有更好的方法吗?

4

2 回答 2

7

我最近遇到了这个问题,在#moose频道上询问后,被告知要这样处理:

标记cache_allowed_valueslazy_build_build_cache_allowed_values引用当前的,并在clearsallowed_values上放置一个写触发器。allowed_valuescache_allowed_values

这样,无论请求或保存值的顺序是什么,它们总是以最少的工作量正确。


例子:

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 => ...,
);
于 2009-10-11T15:40:27.973 回答
2

我认为您真的想allowed_values成为一个具有您想要的效率和排序属性的单独数据结构。既然看起来你并不关心排序,为什么不:

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

一般来说,任何不特定于正在实现的类的东西都应该在其他地方实现。使数组具有更快的查找时间并不是您正在编写的任何类的真正工作,因此它应该在其他地方实现,并且这个类应该使用那个类。(在简单的情况下,就像上面的哈希,也许忽略这个规则是可以的。但如果它更复杂,你肯定想把它排除掉。)

编辑:

如果您希望用户认为这是一个列表,那么:

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,
);

现在您可以设置allowed_values如下:

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

并像这样访问它们:

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

并修改它们:

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

这对用户隐藏了任何底层实现细节。

顺便说一句,实际上是在构建哈希并且使用它比对 3 个元素的线性扫描要快得多吗?

于 2009-10-12T06:29:49.433 回答