Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Re^2: Moose and default values

by tobyink (Abbot)
on Feb 21, 2013 at 13:17 UTC ( #1019944=note: print w/ replies, xml ) Need Help??


in reply to Re: Moose and default values
in thread Moose and default values

Good idea. I don't know why I didn't think of initializers. The Moose cabal aren't especially big fans of initializers, and consider them to be a bit of a misfeature, but this is the kind of niche area they seem to come in handy.

Let's take your initializer technique and add some metaprogramming.

First some test cases...

use v5.14; use Test::More; package Foo { use Moose; has foo => ( traits => ['MooseX::IgnoreFalse::Trait::Attribute'], is => 'ro', default => 'whatever', ); } is(Foo->new(foo => 'xyz')->foo, 'xyz'); is(Foo->new(foo => '0')->foo, 'whatever'); is(Foo->new(foo => '')->foo, 'whatever'); is(Foo->new(foo => undef)->foo, 'whatever'); done_testing;

When those tests all pass, then we're done.

Now, a first stab at the metatrait. For attribute metatraits, after _process_options can be a very handy place to hook onto. It effectively allows you to rewrite a has declaration before the attribute gets created. If you're not doing anything to deeply modify how Moose internals work, then that's often sufficient.

Here we'll just hook after _process_options to add an initializer that does the following:

  1. If the value passed into the constructor was true, then set it.
  2. Otherwise, if there is a default, set the attribute value to the default value.
  3. Otherwise, if there is a builder, set the attribute value to the built value.
  4. Otherwise, don't set the attribute at all.

How does that look?

use v5.14; package MooseX::IgnoreFalse::Trait::Attribute { use Moose::Role; after _process_options => sub { my ($class, $name, $options) = @_; $options->{initializer} = sub { my ($instance, $value, $setter) = @_; my $meta = $instance->meta->get_attribute($name); $value ? $setter->( $value ) : $meta->has_default ? $setter->( $meta->default($instance) +) : $meta->has_builder ? $setter->( $meta->_call_builder($inst +ance) ) : (); } }; }

This is sufficient to pass our test case; yay! However, the keen eyed may have noticed that if the attribute already had an initializer, that will be replaced with our new initializer. So let's make our hook a little smarter to supplement any existing initializer...

use v5.14; package MooseX::IgnoreFalse::Trait::Attribute { use Moose::Role; after _process_options => sub { my ($class, $name, $options) = @_; if (my $orig = $options->{initializer}) { $options->{initializer} = sub { my ($instance, $value, $setter) = @_; my $meta = $instance->meta->get_attribute($name); $value ? $orig->( $instance, $value, $sett +er ) : $meta->has_default ? $orig->( $instance, $meta->defaul +t($instance), $setter ) : $meta->has_builder ? $orig->( $instance, $meta->_call_ +builder($instance), $setter ) : (); } } else { $options->{initializer} = sub { my ($instance, $value, $setter) = @_; my $meta = $instance->meta->get_attribute($name); $value ? $setter->( $value ) : $meta->has_default ? $setter->( $meta->default($instan +ce) ) : $meta->has_builder ? $setter->( $meta->_call_builder($ +instance) ) : (); } } }; }

That's a fairly gentle introduction to attribute metaprogramming.

For completeness, here's everything in one file.

package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name


Comment on Re^2: Moose and default values
Select or Download Code
Re^3: Moose and default values
by 7stud (Deacon) on Feb 22, 2013 at 00:06 UTC

    The Moose cabal aren't especially big fans of initializers, and consider them to be a bit of a misfeature,

    Why is that? The only thing I can find is the following from Moose::Manual:BestPractices:

    Don't use the initializer feature
    
    Don't know what we're talking about? That's fine.
    
    Uh, okay, but I do know what you are talking about. The manual's reasoning for that best practice doesn't quite rise to the level of a Damian Conway best practice.

      "Why is that?"

      Good question. I wasn't sure myself. I seem to recall RJBS saying that initializers were his first ever contribution to Moose, but that he regretted ever adding them.

      The best reasons I could come up with were:

      • It leads to inconsistent behaviour when an attribute value is set via the constructor versus a writer method. (Of course, this isn't an issue for read-only attributes.)

      • Initializers can't be inlined whereas type coercions can be partly compiled into a more efficient form, and have potential for improvement.

      Having asked in #moose, I've had the following additional suggestions:

      • The initializer isn't called until after type constraints have been checked, so initializers don't work for one of the main use cases you'd expect them to be useful for: to munge values to conform to their type constraints.

      • (I'll add to this list when I receive more responses from IRC.)

      package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (12)
As of 2015-07-05 20:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (67 votes), past polls