6

I have a bunch of lazy features in a Moose object.

Some of the builders require some time to finish.

I would like to nvoke all the builders (the dump the "bomplete" object). Can I make all the lazy features be built at once, or must I call each feature manually to cause it builder to run?

David B
  • 29,258
  • 50
  • 133
  • 186
  • 1
    Shouldn't you be using `default` for that? I thought the point of `lazy` was to postpone creating the attribute value until first use. If you need all attributes to be set at object construction, `default` seems more useful. Or, you can provide your own `BUILD` method. – Sinan Ünür Nov 19 '10 at 14:05
  • This is a reasonably common idiom and there are some decent reasons for it. – hobbs Nov 19 '10 at 15:22
  • Basically, you could do all of the work in `BUILD` instead but it's nicer to have a method per-attribute. And if you're going to have a method per attribute to compute the value then it may as well be a builder. But if a builder is going to access other attributes then the built attribute needs to be lazy to be sure that they've been initialized. And there isn't a "lazy, but only a *little bit* lazy" attribute option :) – hobbs Nov 19 '10 at 15:28
  • FWIW, this has come up a few times on #moose, and a new MX module proposed to handle this, somewhat parallel to MooseX::LazyRequire. It would allow marking attributes "this is lazy, because it depends on other attribute values to be built, but I want it to be poked before construction finishes." – Ether Nov 19 '10 at 16:50

3 Answers3

6

If you want to have "lazy" attributes with builders, but ensure that their values are constructed before new returns, the usual thing to do is to call the accessors in BUILD.

sub BUILD {
    my ($self) = @_;

    $self->foo;
    $self->bar;
}

is enough to get the job done, but it's probably best to add a comment as well explaining this apparently useless code to someone who doesn't know the idiom.

hobbs
  • 223,387
  • 19
  • 210
  • 288
3

Maybe you could use the meta class to get list of 'lazy' attributes. For example:

package Test;

use Moose;


has ['attr1', 'attr2'] => ( is => 'rw', lazy_build => 1);
has ['attr3', 'attr4'] => ( is => 'rw',);

sub BUILD {
    my $self = shift;


    my $meta = $self->meta;

         foreach my $attribute_name ( sort $meta->get_attribute_list ) {

         my $attribute =  $meta->get_attribute($attribute_name);

        if ( $attribute->has_builder ) {
            my $code = $self->can($attribute_name);
            $self->$code;

        }
    }

}


    sub _build_attr1 { 1 }
    sub _build_attr2 { 1 }
jira
  • 3,890
  • 3
  • 22
  • 32
2

I've had this exact requirement several times in the past, and today I actually had to do it from the metaclass, which meant no BUILD tweaking allowed. Anyway I felt it would be good to share since it basically does exactly what ether mentioned:

'It would allow marking attributes "this is lazy, because it depends on other attribute values to be built, but I want it to be poked before construction finishes."'

However, derp derp I have no idea how to make a CPAN module so here's some codes: https://gist.github.com/TiMBuS/5787018

Put the above into Late.pm and then you can use it like so:

package Thing;
use Moose;
use Late;

has 'foo' => (
    is      => 'ro',
    default => sub {print "setting foo to 10\n"; 10},
);

has 'bar' => (
    is      => 'ro',
    default => sub {print 'late bar being set to ', $_[0]->foo*2, "\n"; $_[0]->foo*2},
    late    => 1,
);

#If you want..
__PACKAGE__->meta->make_immutable;
1;


package main;

Thing->new();
#`bar` will be initialized to 20 right now, and always after `foo`.
#You can even set `foo` to 'lazy' or 'late' and it will still work.
Jarrod Funnell
  • 721
  • 4
  • 11