Search code examples
perlmethodsmoose

Defining a MX::Declare method as a attribute trigger


The following code works as I'd expect. The cached lazy attribute gets cleared and rebuilt when the foo attribute it depends on is changed.

use MooseX::Declare;
use 5.010;

class Test {
  has foo => ( isa => 'Str', is => 'rw', trigger => sub {my $self = shift; $self->clearer}, default => '' );

  has lazy => ( isa => 'Str', is => 'ro', lazy => 1, clearer => 'clearer',
                default => method { say 'building lazy'; return "foo is '".$self->foo."'"; },
              );

  method say ( ) {
    say $self->lazy;
  }
}

my $inst = Test->new( foo => 'baz' );
$inst->say;
$inst->say;
say $inst->foo();
$inst->foo('bar'); 
$inst->say;

output:

building lazy
foo is 'baz'
foo is 'baz'
baz
building lazy
foo is 'bar'

How do I, however, use the MX::Declare sugar for the trigger subroutine? Defining foo as:

has foo => ( isa => 'Str', is => 'rw', trigger => method {$self->clearer}, default => '' );

Results in the class dying on compilation (below). Am I doing something wrong with my anonymous method declaration?

Trigger must be a CODE ref on attribute (foo) at C:/Strawberry/perl/site/lib/Moose/Meta/Attribute.pm line 423 Moose::Meta::Attribute::_process_trigger_option('Moose::Meta::Attribute', 'foo', 'HASH(0x2a5d14c)') called at C: /Strawberry/perl/site/lib/Moose/Meta/Attribute.pm line 299 Moose::Meta::Attribute::_process_options('Moose::Meta::Attribute', 'foo', 'HASH(0x2a5d14c)') called at C:/Strawb erry/perl/site/lib/Moose/Meta/Attribute.pm line 88 Moose::Meta::Attribute::new('Moose::Meta::Attribute', 'foo', 'trigger', 'MooseX::Method::Signatures::Meta::Metho d=HASH(0x39a421c)', 'isa', 'Str', 'definition_context', 'HASH(0x3452184)', 'default', '', 'is', 'rw') called at C:/Straw berry/perl/site/lib/Moose/Meta/Attribute.pm line 114 Moose::Meta::Attribute::interpolate_class_and_new('Moose::Meta::Attribute', 'foo', 'trigger', 'MooseX::Method::S ignatures::Meta::Method=HASH(0x39a421c)', 'isa', 'Str', 'default', '', 'definition_context', 'HASH(0x3452184)', 'is', 'r w') called at C:/Strawberry/perl/site/lib/Moose/Meta/Class.pm line 704 Moose::Meta::Class::_process_new_attribute('Moose::Meta::Class=HASH(0x38c79d4)', 'foo', 'trigger', 'MooseX::Meth od::Signatures::Meta::Method=HASH(0x39a421c)', 'isa', 'Str', 'default', '', 'definition_context', 'HASH(0x3452184)', 'is ', 'rw') called at C:/Strawberry/perl/site/lib/Moose/Meta/Class.pm line 697 Moose::Meta::Class::_process_attribute('Moose::Meta::Class=HASH(0x38c79d4)', 'foo', 'trigger', 'MooseX::Method:: Signatures::Meta::Method=HASH(0x39a421c)', 'isa', 'Str', 'default', '', 'definition_context', 'HASH(0x3452184)', 'is', ' rw') called at C:/Strawberry/perl/site/lib/Moose/Meta/Class.pm line 566 Moose::Meta::Class::add_attribute('Moose::Meta::Class=HASH(0x38c79d4)', 'foo', 'trigger', 'MooseX::Method::Signa tures::Meta::Method=HASH(0x39a421c)', 'isa', 'Str', 'default', '', 'definition_context', 'HASH(0x3452184)', 'is', 'rw') called at C:/Strawberry/perl/site/lib/Moose.pm line 77 Moose::has('Moose::Meta::Class=HASH(0x38c79d4)', 'foo', 'isa', 'Str', 'is', 'rw', 'trigger', 'MooseX::Method::Si gnatures::Meta::Method=HASH(0x39a421c)', 'default', '') called at C:/Strawberry/perl/site/lib/Moose/Exporter.pm line 356

    Moose::has('foo', 'isa', 'Str', 'is', 'rw', 'trigger', 'MooseX::Method::Signatures::Meta::Method=HASH(0x39a421c) ',

'default', '') called at mx_declare.pl line 5 main::ANON() called at C:/Strawberry/perl/site/lib/MooseX/Declare/Syntax/MooseSetup.pm line 81 MooseX::Declare::Syntax::MooseSetup::ANON('CODE(0x38c3a94)') called at mx_declare.pl line 13


Solution

  • The method keyword returns an instance of the MooseX::Method::Signatures::Meta::Method class, which is a subclass of Moose::Meta::Method, which is a subclass of Class::MOP::Method.

    Moose allows a method object for default, but not for trigger, which must be a regular coderef.

    If you really want to use the method keyword there, you could probably do:

    trigger => method { $self->clearer }->body,
    

    But it's probably easier (and saner) to do what @cjm suggests and just use a regular coderef:

    trigger => sub { shift->clearer },