http://www.perlmonks.org?node_id=889702


in reply to Simple Mouse/Moose question

So do_n(), the builder function, fires, then do_n2() fires "after". However, while do_n2() is provided with the object instance this instance does not contain the value for n which was initialized by the builder

Pretty much any time you want the builder/default of one attribute to have access to the values of another attribute, you want to use lazy. The order of attribute initialization in Moose (and Mouse) is undefined (basically hash-order). When you use lazy this allows the order to essentially be determined by the dependency chain itself. What you are doing here is pretty much more easily done like this:

#!/usr/bin/perl -w use strict; package Test; use Mouse; has 'n' => (is => 'rw', isa => 'Int', lazy => 1, builder => 'do_n'); has 'n2' => (is => 'rw', isa => 'Int', lazy => 1, builder => 'do_n2'); sub do_n { my $s = shift; print "In do_n() \$s = $s\n"; return 3; } sub do_n2 { my $s = shift; print "In do_n2() \$s = $s\n"; return $s->n + 2; } __PACKAGE__->meta->make_immutable(); package main; use Data::Dumper; my $test = Test->new(); warn Dumper $test; $test->n2; warn Dumper $test;
which produces the following output:
$VAR1 = bless( {}, 'Test' ); In do_n2() $s = Test=HASH(0x861360) In do_n() $s = Test=HASH(0x861360) $VAR1 = bless( { 'n' => 3, 'n2' => 5 }, 'Test' );

-stvn

Replies are listed 'Best First'.
Re^2: Simple Mouse/Moose question
by ikegami (Patriarch) on Feb 23, 2011 at 01:50 UTC

    lazy => 1 is unnecessary on "n" (since it doesn't rely on anything), which is why I left out. It may still be useful to leave it on to save memory, though.

    The only other difference between our code is that I inlined the default builders. I don't think having to look somewhere else in the file for that code is appropriate.

      lazy => 1 is unnecessary on "n" (since it doesn't rely on anything), which is why I left out.

      True, the reason i put it in though was mostly future-proofing. It doesn't add much overhead (it really just spread it out actually) and I find that having everything in the dependency chain be lazy just makes things easier to refactor and tweak later on.

      The only other difference between our code is that I inlined the default builders. I don't think having to look somewhere else in the file for that code is appropriate.

      Personally I agree, I tend to use default more then I use builder. In fact, I only use builder when I want to easily allow subclassing of the builder method.

      -stvn
Re^2: Simple Mouse/Moose question
by halfcountplus (Hermit) on Feb 23, 2011 at 13:50 UTC

    >>Pretty much any time you want the builder/default of one attribute to have access to the values of another attribute, you want to use lazy. The order of attribute initialization in Moose (and Mouse) is undefined (basically hash-order).

    Okay, but that is not what is going on in my example. There is only one builder method; the second method is called via the modifier "after" which guarantees do_n() already happened. Ie, do_n2() is only called "after" do_n().

    The problem does not matter much since there are other ways to accomplish the goal (eg, assigning do_n2() as a lazy builder method works). It just makes me suspicious of the use value of the Moose style method modifiers: if I assign an "after" method but changes made to the object in the triggering function have not take place when its "after" method is called, does that not make using such a method modifier very problematic???? Eg, from Moose::Manual::MethodModifiers:

    Similarly, an after modifier could be used for logging an action that was taken.

    I guess it could be used, but let's say the action taken was that a string value was changed, and I call an after modifier method to record the change -- BUT the value is still the same as it was prior to to the action that "was" taken, then how can this modifier method log the change correctly?? It will record the previous value, not the new one, since the object it receives does not reflect assignments made in the method it "modifies after".

      Okay, but that is not what is going on in my example. There is only one builder method; the second method is called via the modifier "after" which guarantees do_n() already happened. Ie, do_n2() is only called "after" do_n(). The problem does not matter much since there are other ways to accomplish the goal (eg, assigning do_n2() as a lazy builder method work

      Okay, I see what you are saying and I see what the issue is now.

      So what is happening is that the builder method do_n is being called, and the return value of that method is then taken an assign to the 'n' slot. Your method modifier is called after the builder method, meaning, it happens before the actual slot assignment, hence 'n' always being null.

      I usually don't recommend using method modifiers with things like builders since it can get very hairy and really, that is what lazy & default/builder are for (as you discovered with your lazy_builder solution).

      It just makes me suspicious of the use value of the Moose style method modifiers

      Well, there are plenty more uses then just this :)

      If I assign an "after" method but changes made to the object in the triggering function have not take place when its "after" method is called, does that not make using such a method modifier very problematic????

      Yes it would, but as I pointed out above, the modifier didn't fire after, it fired before. The builder method is not the right place to wrap this stuff. Moose does not perform any magic in this sense, if you have assigned the slot (or called the accessor to assign it for you), then the change will be there. I think this was just a simple misunderstanding of when builder is fired in the chain.

      I guess it could be used, but let's say the action taken was that a string value was changed, and I call an after modifier method to record the change -- BUT the value is still the same as it was prior to to the action that "was" taken, then how can this modifier method log the change correctly?? It will record the previous value, not the new one, since the object it receives does not reflect assignments made in the method it "modifies after".

      It is outside of the responsibility of Moose to decide if your value actually changed or not, Moose only knows (and only cares) that you called a method, PERIOD. If you want this kind of thing you will need to write it yourself on top of Moose, as a MooseX:: module perhaps, but it most certainly does not belong in Core Moose, if for nothing more then the notions of equality are not universally agreed upon.

      -stvn

      the second method is called via the modifier "after" which guarantees do_n() already happened.

      Where do you see do_n assigning anything to "n"? As I explained, "after do_n already happened" is too soon. The "trigger" is "as soon as it returns".

      $self->{n} = sub { my ($self) = @_; my $rv = $self->do_n(); $self->do_n2(); # Too soon return $rv; }->($self);