Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

attribute initialisation and after

21 views
Skip to first unread message

Dave Howorth

unread,
Oct 5, 2012, 10:24:04 AM10/5/12
to mo...@perl.org
I wrote a Moose class and the class 'has' an attribute. The attribute
also has an 'after' modifier.

But when I create an object of the class and supply an initial value for
the attribute to the 'new' method, it appears that the 'after' modifier
is not called. It is called if I later access the attribute.

My expectation is that initialisation of an attribute would invoke the
attribute's setter and the modifier, so reality doesn't match my
expectation - is there something that explains why reality is as it is
so I can adjust my expectation?

Cheers, Dave

Jeff Hallock

unread,
Oct 5, 2012, 10:27:34 AM10/5/12
to Dave Howorth, mo...@perl.org
Hi Dave,

'after' is called after a method call.

If you want a block of code to be called every time you set an attribute, you want to use a trigger:

has 'foo' => (
is => 'rw',
trigger => sub {
# will be called when setting the attribute via 'new' or via the writer method
my ( $self, $newval, $oldval ) = @_;
...
}
);

- Jeff

Dave Howorth

unread,
Oct 5, 2012, 10:54:15 AM10/5/12
to mo...@perl.org
Jeff Hallock wrote:
> Hi Dave,
>
> 'after' is called after a method call.
>
> If you want a block of code to be called every time you set an attribute, you want to use a trigger:
>
> has 'foo' => (
> is => 'rw',
> trigger => sub {
> # will be called when setting the attribute via 'new' or via the writer method
> my ( $self, $newval, $oldval ) = @_;
> ...
> }
> );

That's great. My code works now. Thanks Jeff.

It does seem counter-intuitive. I'd expect

$o = Class->new(attribute => 'value');

to have the same effect as

$o = Class->new(); $o->attribute('value');

Anyway, at least it works and hopefully I'll remember the exception for
next time.

Cheers, Dave

Chris Prather

unread,
Oct 5, 2012, 12:16:21 PM10/5/12
to Dave Howorth, mo...@perl.org
I actually wouldn't, at least not anymore.

Behavior and state while related are distinct. The call to new() is
passing in the new state for the object that is about to be created, I
wouldn't expect any extra behavior to fire there, and if it did I
would want it to happen explicitly (say via $self->attribute('value')
in BUILD method ... or via a trigger).

I can understand why you would have the expectations you have. I can
also understand why I have the expectations I have. Typically (though
arguably not always) with Moose the defaults should be set to the
least intrusive / most explicit possible. Since there is no way to
unfire an accessor method during construction, the default has to be
to make that behavior explicit.

-Chris

Dave Howorth

unread,
Oct 8, 2012, 5:32:28 AM10/8/12
to mo...@perl.org
Chris Prather wrote:
> On Fri, Oct 5, 2012 at 10:54 AM, Dave Howorth
> <dhow...@mrc-lmb.cam.ac.uk> wrote:
>> Jeff Hallock wrote:
>>> Hi Dave,
>>>
>>> 'after' is called after a method call.
>>>
>>> If you want a block of code to be called every time you set an attribute, you want to use a trigger:
>>>
>>> has 'foo' => (
>>> is => 'rw',
>>> trigger => sub {
>>> # will be called when setting the attribute via 'new' or via the writer method
>>> my ( $self, $newval, $oldval ) = @_;
>>> ...
>>> }
>>> );
>> That's great. My code works now. Thanks Jeff.
>>
>> It does seem counter-intuitive. I'd expect
>>
>> $o = Class->new(attribute => 'value');
>>
>> to have the same effect as
>>
>> $o = Class->new(); $o->attribute('value');
>>
>> Anyway, at least it works and hopefully I'll remember the exception for
>> next time.
>
> I actually wouldn't, at least not anymore.

I'm afraid I don't understand what it is you wouldn't, given that it
seems you would have before but won't now?

> Behavior and state while related are distinct. The call to new() is
> passing in the new state for the object that is about to be created, I
> wouldn't expect any extra behavior to fire there, and if it did I
> would want it to happen explicitly (say via $self->attribute('value')
> in BUILD method ... or via a trigger).

The call to new is passing in a new value for an attribute.
The call to the mutator is passing in a new value for an attribute.
I expect them to treat the new value in the same way in both cases.

> I can understand why you would have the expectations you have. I can
> also understand why I have the expectations I have.

I don't understand why you have the expectations you have. I'm not even
sure I understand what those expectations are. Is there any
documentation that explain what expectations are sensible to have for Moose?

(one thing I've noticed is that the documents start out by giving basic
explanations of how to construct objects, and then more complicated
explanations of what can be constructed, without giving any examples at
all of how to use the objects.)

> Typically (though
> arguably not always) with Moose the defaults should be set to the
> least intrusive / most explicit possible. Since there is no way to
> unfire an accessor method during construction, the default has to be
> to make that behavior explicit.

I'm not clear what defaults have to do with it? Nor what would
constitute unfiring an accessor? Or why you would want to do it?

Confused, Dave

Jesse Luehrs

unread,
Oct 8, 2012, 11:03:24 AM10/8/12
to Dave Howorth, mo...@perl.org
But what if the attribute doesn't have a writable accessor? Being able
to set attributes in the constructor that then can't be changed after
creation is a very common pattern. If all constructor parameters had to
go through accessors, this wouldn't be possible.

This is also quite common in non-Moose classes - typically, constructors
will do something along the lines of

sub new {
my $class = shift;
my %params = @_;

return bless {
foo => $params{foo},
bar => $params{bar},
}, $class;
}

and what Moose does is not significantly different from this.

> > I can understand why you would have the expectations you have. I can
> > also understand why I have the expectations I have.
>
> I don't understand why you have the expectations you have. I'm not even
> sure I understand what those expectations are. Is there any
> documentation that explain what expectations are sensible to have for Moose?
>
> (one thing I've noticed is that the documents start out by giving basic
> explanations of how to construct objects, and then more complicated
> explanations of what can be constructed, without giving any examples at
> all of how to use the objects.)

This is because once you create the objects, they should be usable just
as any other Perl object. If there is a place in the documentation that
you think could be improved, we are always open to documentation patches
- doc patches from new users tend to be very helpful because they still
know which parts are confusing(:

-doy

Chris Prather

unread,
Oct 8, 2012, 11:52:38 AM10/8/12
to Dave Howorth, mo...@perl.org
After I wrote the first response I realized I had never actually
written a class to work the way you expected, though I still do
understand why you would expect that. Even in the pre-moose days
though I'd write constructors like so:

sub new {
my $self = shift;
my $args = @_ > 1 ? { @_ } : $_[0];
my $class = ref $self || $self;
return bless $args, $class;
}

Which doesn't actually behave the way you expect either.

>> Behavior and state while related are distinct. The call to new() is
>> passing in the new state for the object that is about to be created, I
>> wouldn't expect any extra behavior to fire there, and if it did I
>> would want it to happen explicitly (say via $self->attribute('value')
>> in BUILD method ... or via a trigger).
>
> The call to new is passing in a new value for an attribute.
> The call to the mutator is passing in a new value for an attribute.
> I expect them to treat the new value in the same way in both cases.

Well sure, but what about the case where you have no mutator?

has foo => ( isa => 'HashRef', traits => ['Hash'], handles => {
... } ); # look ma no mutator

How would you explain the expected behavior then?

How would you explain that state and behavior are isomorphic?

has value => ( is => 'ro', lazy => 1, default => sub {
long_calculation_in_c() } );

vs

sub value { long_calculation_in_c() }

I wouldn't expect long_calculation_in_c() to be called if I call
->new(value => $test_value) in the first case ... would you? Is the
default part of the mutator?

What about mutators with side effects?

use MooseX::Hypothetical::LockableAttributes;
has value => ( is => 'rw', traits => ['LockOnChange'], lock_timeout => 30 );

The many many variations on mutators is what makes the idea that
attributes and mutators are *different* important. It's the difference
between state (attributes) and behavior (methods ... in this case
accessors / mutators).

>> I can understand why you would have the expectations you have. I can
>> also understand why I have the expectations I have.
>
> I don't understand why you have the expectations you have. I'm not even
> sure I understand what those expectations are. Is there any
> documentation that explain what expectations are sensible to have for Moose?

Moose::Manual::Unsweetened (
https://metacpan.org/module/Moose::Manual::Unsweetened ) goes a long
way to describing the expected outcome of Moose. Things can always be
better.

> (one thing I've noticed is that the documents start out by giving basic
> explanations of how to construct objects, and then more complicated
> explanations of what can be constructed, without giving any examples at
> all of how to use the objects.)

As Jesse (doy) has said elsewhere in this thread, please if you find
something confusing or you can think of a way to clarify something ...
write a doc patch. The fact that our expectations differ from yours
suggests strongly that we're not the right people to write such a
patch.

>> Typically (though
>> arguably not always) with Moose the defaults should be set to the
>> least intrusive / most explicit possible. Since there is no way to
>> unfire an accessor method during construction, the default has to be
>> to make that behavior explicit.
>
> I'm not clear what defaults have to do with it? Nor what would
> constitute unfiring an accessor? Or why you would want to do it?

Not "defaults" in the attribute sense ... defaults in the sense of
"What Moose does without someone altering the behavior by using a
MooseX or other extension". As so why you would want to unfire an
accessor, in the system you described where Class->new( foo => 'bar' )
is exactly equivalent to $o = Class->new(); $o->foo('bar'); any
side-effects that calling the foo() method are unavoidable ... this
makes writing some kinds of accessors more difficult. Moose's
base-line behavior is typically to provide the least intrusive
behavior and let people write out the differences themselves in
extension modules etc.

-Chris
0 new messages