2009-11-21 1 views
5

В наших классах мы имеем шаблон, в котором мы создаем атрибут для представления расчетного значения . По очевидным причинам мы хотим кэшировать вычисленное значение и затем аннулировать кеш при изменении одного из базовых значений.Moose: истекающие кешированные результаты вычислений при изменении значений атрибутов?

Таким образом, мы в настоящее время это:

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

Этот длинный метод рука становится очень утомительным и ошибками при вычислении значения зависит от других расчетных значений.

Есть ли более умный/простой способ для «бара», чтобы контролировать атрибуты, которые он зависит от , и «foo» знает, кто на этом зависит? Также как я могу избежать установки строки через хэш доступ к членству?

ответ

5

Я думаю, что это вполне возможно, что вы делаете это тяжелее на себя, используя атрибуты неявного запоминания с ленивым, когда вы могли бы просто сделать запоминание явно делая ваш вся программа более прозрачной

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 
} 

См CPAN-х Memoize информации и контроля внутреннего кэша, а также помнить, что Memoized функция не может зависеть от состояния объекта. Поэтому аргументы должны быть переданы явно.

+0

Hm. У меня возникают проблемы с использованием Memoize для кэширования данных объекта. Что произойдет, если каждый экземпляр этого класса имеет разные значения? Memoize будет кэшировать их навсегда независимо от того, что они больше не полезны, когда объект уничтожен, правильно? Это означает, что в постоянном приложении (и это действительно единственное разумное место для использования Moose) вы потенциально собираетесь выращивать огромный бесполезный кеш. Нет? Конечно, вы можете беспорядочно уничтожать вещи вручную (я думаю!), Но это намного сложнее, чем выше пример Moose/lazy, для небольшого выигрыша. – Dan

+1

Я принципиально не согласен, это не просто это/меньше/сложно, более прозрачный, но скорость и усиление скорости предсказуемы, и логика заключается в том, где она должна быть, а не взломана в других устройствах доступа. Все, что вам нужно сделать, это подкласс Memoize :: Expire и установить подставку STORE, чтобы очистить кеш, прежде чем писать в хэш. –

+1

Я выбрал это как ответ, поскольку он радикально упрощает код, к чему я действительно стремился. Тот факт, что результат вычисления не хранится в самом объекте, не является проблемой для моей текущей реализации. Thanks EvanCaroll. – clscott

11

Если вы правильно поняли, вы можете использовать triggers, чтобы очистить атрибуты, если они установлены. Вот пример:

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

Таким образом, любые записи в foo через $obj->foo($newvalue) заставит bar быть очищены и заново на следующий доступ.

+1

Я голосовал, потому что это способ Музыки, который требует написания наименее вуду, но я думаю, что мое решение намного лучше. Я также хотел указать на очевидную сторону этого, ваша рабочая нагрузка растет с каждым изменением атрибута, потому что каждое изменение атрибута должно явно очищать ленивый (рассчитанный) атрибут. Если у него есть 5 атрибутов, и он изменяет каждый раз 5 раз, прежде чем он называет ленивый атрибут, который является 24 потерянными вызовами, чтобы очистить ленивый (рассчитанный) атрибут. Это также злоупотребляет ленивыми, чтобы получить выгоду от воспоминаний. –

+2

Это также обратная сторона с точки зрения ремонтопригодности; bar-depends-on-foo логически является свойством бара, а не foo – ysth

+0

Это способ Moose-y и отличный ответ, но я был счастлив вырезать любой дополнительный код на основе лося, который только что мешал бизнес-логике , Спасибо, Дэн. – clscott

0

Будет ли это работать?

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

Выход:

$ perl test.pl 
update b 
update c 
+1

Я не уверен, почему вы думаете, что он хочет установить b, из. но это катастрофа круговых триггеров в процессе создания. Поскольку вы устанавливаете его через предоставленные лосями публичные интерфейсы, а не мета-случайное изменение в b через сеттер, это приведет к тому, что его триггер установит значение, которое вызовет его триггер ... Настройка атрибутов через триггеры с использованием общедоступного интерфейса это плохая идея. –

+0

Установка '$ b' из' $ a' была способом сказать, что он может обновить вычисленное значение ('$ b'), когда изменяется одно из основных значений (' $ a'). И я не думаю, что будет триггерный цикл, если он просто хочет обновить вычисленные свойства. Может быть, я просто не получаю ваш аргумент - есть пример? – zoul

+2

(Но, конечно, это решение хуже, чем выше, потому что оно не пересчитывает '$ b' лениво.) – zoul

0

Я не делал никаких попыток прокручиваться внутри внутренних элементов Moose и протокола метаобъектов, но я думаю, что это подходящее время для этого.

Вы хотите, чтобы исправить генерацию кода, так что при указании атрибута в

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

фазы генерации кода создает код для атрибутов foo и bar, как было указано выше.

Как это сделать, это упражнение, оставленное читателю. Если бы у меня была подсказка, я постараюсь дать вам начало. К сожалению, все, что я могу вам посоветовать, это «Это работа для СС».

+0

Это больше, чем ответ, который я принял. Это также кажется непрактичным, поскольку для этого требуется тестирование и обслуживание модуля MooseX :: некоторый тип плагина или патч против самого Moose, который никогда не будет принят в ядро. – clscott

+0

Ознакомьтесь с разделами «Расширение» и «Мета» в кулинарной книге. Сначала это кажется страшным. Когда вы читаете документы, это выглядит не так уж плохо. Независимо от того, как вы решаете свою проблему, главное - минимизировать неудобный код, который вы должны поддерживать. Если мета-подход делает это, то хорошо. В противном случае используйте что-то еще. – daotoad

 Смежные вопросы

  • Нет связанных вопросов^_^