How can I make all lazy Moose features be built?
Asked Answered
B

3

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?

Begum answered 19/11, 2010 at 14:0 Comment(4)
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.Gizzard
This is a reasonably common idiom and there are some decent reasons for it.Epistyle
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 :)Epistyle
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."Klos
E
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.

Epistyle answered 19/11, 2010 at 15:25 Comment(0)
S
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 }
Sartre answered 20/11, 2010 at 0:19 Comment(0)
S
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.
Swanskin answered 12/6, 2013 at 10:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.