Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Moose and BUILD

by tj_thompson (Monk)
on Jun 15, 2011 at 19:20 UTC ( #909840=perlquestion: print w/ replies, xml ) Need Help??
tj_thompson has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks, I'm back with a Moose question that's causing me some head scratching. I have the following code:

package Foo; use Moose; has 'y' => ( is => 'Num', is => 'rw' ); has 'x' => ( is => 'Num', is => 'rw' ); sub init_y { my ($self, $value) = @_; $self->y(sqrt($self->x)); } sub BUILD { my ($self) = @_; print STDERR "Foo::BUILD running\n"; $self->init_y; } 1;

And:

package Foo::Bar; use Moose; extends 'Foo'; before 'BUILD' => sub { my $self = shift; print STDERR "Foo::Bar before BUILD running\n"; $self->x(25); }; 1;

And my test script:

use lib '.'; use Foo::Bar; use Data::Dumper; my $o = Foo::Bar->new; print STDERR Dumper($o);
My output is this:
plxc16479> ex.pl Foo::BUILD running Use of uninitialized value in sqrt at Foo.pm line 17. Foo::Bar before BUILD running Foo::BUILD running $VAR1 = bless( { 'y' => '5', 'x' => 25 }, 'Foo::Bar' );

So, my question is...why am I seeing Foo::BUILD execute twice? My guess is that Foo::Bar is inheriting Foo's BUILD method...meaning there is now Foo::Build and Foo::Bar::BUILD. The base Foo::Build executes. Since Foo::Bar has a before BUILD modifier, and an inherited BUILD method, Foo::Bar before BUILD is executed before the subclass Foo::Bar::BUILD...but after Foo::BUILD has already done its thing.

Assuming this is the case, how do I get the behavior that I want? The idea is that Foo::Bar can modify the way Foo initializes by presetting 'x' to a particular value. However, I seem unable to do this with a sub class before Foo::BUILD executes. Here, that results in a warning but it can definitely result in problems in real code.

Comment on Moose and BUILD
Select or Download Code
Re: Moose and BUILD
by moritz (Cardinal) on Jun 15, 2011 at 20:00 UTC

    Maybe you should start with what you want to achieve in the end.

    If y always depends on x, don't make it an attribute, but rather a method that calculates the value of y.

    If you want to make it a default, use Moose's default facility:

    use Moose; has 'y' => ( is => 'Num', is => 'rw' ); has 'x' => ( is => 'Num', is => 'rw', default => sub { sqrt(shift->x) }, );

    I'm not a Moose specialist, but I guess that one reason the code does not do what you want is that you try to call methods on objects that haven't been constructed yet.

    I also don't see why you want to call a derived BUILD method before the parent BUILD method. Just let the parent construct the parent part of the object, and then do whatever you want in the child.

      My example above is very simple code in which transforming the input 'x' into the final attribute value 'y' is trivial. Assume instead it's not trivial to transform the input 'x' into the final value 'y'. In this case, you would be doing the transform work twice if you build the parent, then build the child and change the value the parent arrived at. It could be done, but it strikes me as poor code.

      My concept here, and perhaps I'm approaching it wrong, is that if the base class were to be instantiated, the input 'x' value would be provided. However, if the sub class were instead instantiated, I would like to lock a particular input value of 'x' in.

      It looks like maybe a better way to do this is to instead hook into BUILDARGS as this is executed before the object is created and allows for argument modification. I could explicitly set the value of x passed into the BUILD method and set an empty build method for Foo::Bar.
        Modifying Foo::Bar to this:
        package Foo::Bar; use Moose; extends 'Foo'; use Data::Dumper; around 'BUILDARGS' => sub { my ($orig, $class, %args) = @_; print STDERR "Foo::Bar::BUILDARGS running.\n"; $args{x} = 25; return $class->$orig(%args); }; 1;

        Seems to do what I want. Foo::BUILD is only executed once and it receives the updated 'x' value prior to BUILD execution, so 'y' is only calculated once. Here is the output in this case:

        plxc16479> ex.pl Foo::Bar::BUILDARGS running. Foo::BUILD running $VAR1 = bless( { 'y' => '5', 'x' => 25 }, 'Foo::Bar' );

      The type is specified using isa, and you need lazy => 1 to make sure the default isn't calculated too soon.

      use Moose; has 'x' => ( is => 'rw' isa => 'Num', ); has 'y' => ( is => 'rw', isa => 'Num', lazy => 1, default => sub { sqrt($_[0])->x) }, );

        Good catch on the is, just a brain typo from rapidly throwing together running example code. It didn't matter in this case as I wasn't trying to use bad values in my test script.

        I will look into the lazy flag on the attribute and get a better idea of what it does.

Re: Moose and BUILD
by ikegami (Pope) on Jun 15, 2011 at 20:58 UTC

    So, my question is...why am I seeing Foo::BUILD execute twice?

    Because you used

    before 'BUILD' => sub {
    instead of
    sub BUILD {

    The BUILD of each class is called, from base down. It's not a virtual method.

      Is my assessment in the last paragraph of my original post correct? I purposefully intended to use a before modifier as opposed to an entire new BUILD method. My hope was the before modifier would be applied to the parent BUILD method since the child did not have a BUILD method. I didn't realize I would end up running both parent and child BUILDs independently.

      I do find it odd that in my reply above with the around BUILDARGS modifier, the Foo::BUILD method is only executed once. I would have expected the child to inherit the BUILD method and execute it also (thus required an empty BUILD method in the child if I wanted no further action taken at that time).

      Why does the child seem to inherit (and run) Foo::BUILD when I give a before BUILD modifier in it, but not inherit (or run) BUILD when I do not provide a modifier on the method?

        Is my assessment in the last paragraph of my original post correct?

        You already disproved it here

        I do find it odd that in my reply above with the around BUILDARGS modifier,

        BUILDARGS should be chained from child to base. BUILD should be chained from base to child.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://909840]
Approved by planetscape
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (14)
As of 2014-09-23 14:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (224 votes), past polls