Как мне лучше создать триггерные аксессоры со значениями по умолчанию в Moose? - PullRequest
7 голосов
/ 11 октября 2009

У меня есть ситуация, когда я хотел бы кэшировать некоторые вычисления для использования потом. Допустим, у меня есть список допустимых значений. Так как я собираюсь проверять, есть ли что-нибудь в этом списке, я буду хотеть это как хэш для эффективности и удобства. В противном случае мне пришлось бы 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 не вызывают, когда значение по умолчанию установлено, но оно мешает. Мне не нравится дублирование там.

Есть ли лучший способ сделать это?

Ответы [ 2 ]

7 голосов
/ 11 октября 2009

Я недавно столкнулся с этим, и после того, как спросил на канале #moose, мне сказали обработать это следующим образом:

Пометить cache_allowed_values как lazy_build, иметь _build_cache_allowed_values ссылку на текущий allowed_values и установить триггер записи на allowed_values, который очищает cache_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 => ...,
);
2 голосов
/ 12 октября 2009

Я думаю, вы действительно хотите, чтобы 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 );
}

В общем, все, что не относится к реализуемому классу, должно быть реализовано в другом месте. Создание более быстрого времени поиска для массивов не является задачей любого класса, который вы пишете, поэтому он должен быть реализован в другом месте, и этот класс должен использовать этот класс. (В простом случае, как вышеописанный хеш, возможно, можно игнорировать это правило. Но если бы оно было более сложным, вы бы определенно захотели его разложить.)

Edit:

Если вы хотите, чтобы пользователь думал, что это список, как насчет:

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 элементам?

...