2009-11-21 18 views
5

En nuestras clases tenemos un patrón donde creamos un atributo para representar un valor calculado . Por razones obvias, queremos almacenar en caché el valor calculado y luego invalidar la caché cuando cambia uno de los valores subyacentes.Moose: ¿Caducan los resultados en caché de los cálculos cuando cambian los valores de los atributos?

Así que actualmente tienen la siguiente:

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

Este método de la mano larga se está volviendo muy tedioso y propenso a errores cuando se calculan los valores dependen de otros valores calculados.

¿Existe alguna manera más inteligente/más simple para que 'bar' controle los atributos, depende de y de que 'foo' sepa quién depende de ello? ¿También cómo puedo evitar establecer la barra a través del acceso de miembro hash ?

Respuesta

5

Creo que es muy posible que usted está haciendo esto más difícil en sí mismo mediante el uso de un memoization implícita con perezoso, Atributos cuando uno podía hacer lo que el memoization explícita su todo el programa más transparente

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 
} 

Sede de Memoize de información y control de la caché interna CPAN, también recordar que un memoized la función no puede depender del estado del objeto. Por lo tanto, los argumentos se deben pasar explícitamente.

+0

Hm. Tengo problemas para usar Memoize para almacenar en caché los datos del objeto. ¿Qué pasa si cada instancia de esta clase tiene valores diferentes? Memoize los almacenará en caché para siempre, independientemente de que ya no sean útiles cuando se destruya el objeto, ¿verdad? Lo que significa que en una aplicación persistente (y ese es realmente el único lugar sensato para usar Moose) es posible que crezca un caché enorme e inútil. ¿No? Por supuesto, puedes cometer un error al expirar cosas manualmente (¡creo!), Pero eso es mucho más complejo que el ejemplo anterior de Moose/perezoso, con poca ganancia ... – Dan

+1

Estoy en desacuerdo fundamentalmente, no solo es/menos/complejo y más transparente, pero el golpe y la ganancia de velocidad son predecibles y la lógica es donde debería estar, en lugar de piratearse, en otros accesores. Todo lo que necesita hacer es la subclase Memoize :: Expire y establecer el sub STORE para borrar el caché, antes de escribir en el hash. –

+1

Elegí esto como la respuesta, ya que simplifica drásticamente el código, que era lo que realmente estaba buscando. El hecho de que el resultado del cálculo no se almacena en el objeto en sí no es un problema para mi implementación actual. Gracias EvanCaroll. – clscott

11

Si lo entiendo correctamente, puede usar triggers para borrar los atributos cuando uno está configurado. He aquí un ejemplo:

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

Por lo tanto, cualquier escritura a través de foo$obj->foo($newvalue) causarán bar que desea borrar, y se recrea en la siguiente acceso.

+1

He votado, porque esta es la forma de Moose que requiere escribir el vudú menos, pero creo que mi solución es mucho mejor. También quería señalar el inconveniente obvio de esto, su carga de trabajo aumenta con cada cambio de atributo porque cada cambio de atributo debe borrar explícitamente el atributo perezoso (calculado). Si tiene 5 atributos, y cambia cada uno 5 veces antes de llamar al atributo perezoso que es 24 llamadas desperdiciadas para borrar el atributo perezoso (calculado). Esto también es abusivo para obtener el beneficio de la memorización. –

+2

También está al revés desde una perspectiva de mantenimiento; bar-depends-on-foo es lógicamente una propiedad de la barra, no foo – ysth

+0

Esta es una forma de Moose-y una gran respuesta, pero me alegré de recortar cualquier código extra basado en alces que se interpusiera en el camino de la lógica comercial . Gracias Dan. – clscott

0

¿Funcionaría?

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

Salida:

$ perl test.pl 
update b 
update c 
+1

No estoy seguro de por qué crees que quiere establecer b, desde a. pero este es un desastre de disparadores circulares en ciernes. Dado que lo está configurando a través de las interfaces públicas provistas por alces en lugar de meta, un cambio accidental en b a través del colocador hará que su desencadenador se establezca en un punto que provocará su desencadenador ... Establecimiento de atributos a través de desencadenadores utilizando la interfaz pública es una mala idea –

+0

El ajuste '$ b' de' $ a' era una forma de decir que puede actualizar el valor calculado ('$ b') cuando cambia uno de los valores maestros (' $ a'). Y no creo que haya un ciclo desencadenante si simplemente quiere actualizar las propiedades calculadas. Puede ser que simplemente no tenga su argumento, ¿tengo un ejemplo? – zoul

+2

(Pero ciertamente esta solución es peor que las anteriores debido a que no recalcula '$ b' perezosamente.) – zoul

0

No he hecho ningún hurgamiento en internos de Moose y el protocolo meta objeto, pero creo que este es un buen momento para hacerlo.

desea modificar la generación de código para que cuando se especifica un atributo como

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

la fase de generación de código crea código para los foo y bar atributos según lo especificado anteriormente.

Cómo hacer esto es un ejercicio dejado al lector. Si tuviera una pista, trataría de comenzar. Lamentablemente, todo lo que puedo aconsejarte es "Este es un trabajo para el MOP".

+0

Esto es mucho más trabajo que la respuesta que acepté. También parece poco práctico, ya que requiere la prueba y el mantenimiento de un módulo MooseX ::, algún tipo de complemento o un parche contra Moose que nunca será aceptado en el núcleo. – clscott

+0

Echa un vistazo a las secciones 'Extensión 'y' Meta' en el libro de cocina. Parece aterrador al principio. Cuando lees los documentos, no se ve tan mal. No importa cómo resuelva su problema, lo principal es minimizar el código incómodo que tiene que mantener. Si un enfoque meta hace esto, entonces bien. De lo contrario, usa algo más. – daotoad

Cuestiones relacionadas