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

morgon has asked for the wisdom of the Perl Monks concerning the following question:

Monks,

assume you have a script that takes input from the command-line and passes that on to a Moose-class Foo, i.e.

use Foo; my ($bar) = @ARGV; my $foo = Foo->new( bar => $bar );
When no bar-value is supplied on the command-line the class should use a default instead (I want to keep the defaults in the class-definition not in the script).

A first try does not what I want:

package Foo; use Moose; has bar => (is => "ro", default => "whatever");
because then when the command-line is empty the constructor is called with "bar" => undef, setting the attribute to undef and not to the intended default.

I usually do this instead:

package Foo; use Moose; has a => (is => "rw"); sub BUILD { my($this, $args)=@_; $this->bar("whatever") unless defined $args->{bar}; }
This does work, instantiating via Foo->new( bar => undef ) sets bar to the default, but the downside is that the default-value is not declared with the attribute but buried in the BUILDer-code and to make this work the attribute has to be "rw".

So I wonder if there is a better way to achieve this - i.e. declaratively defining attribute-defaults for ro-attributes that also get applied when undef is passed in.

Many thanks!

Replies are listed 'Best First'.
Re: Moose and default values
by tobyink (Canon) on Feb 20, 2013 at 22:25 UTC

    I wrote PerlX::Maybe precisely for this purpose!

    use v5.14; use strict; use warnings; use PerlX::Maybe; package Foo { use Moose; has foo => (is => 'ro', default => 'whatever'); } my $value = 'hello'; print Foo->new(maybe foo => $value)->dump; $value = undef; print Foo->new(maybe foo => $value)->dump;

    As you can see, the maybe keyword is applied at the "consumer side" - i.e. in the code that is using the class.

    Within the class itself, you could try MooseX::UndefTolerant though that has a major caveat listed in the documentation!

    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
      Nice, but why does it live in "PerlX" and not in "Moose" or "MooseX"?
        Probably you can use it outside Moose, too.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: Moose and default values
by 7stud (Deacon) on Feb 21, 2013 at 05:39 UTC

    ...but the downside is that the default-value is not declared with the attribute but buried in the BUILDer-code and to make this work the attribute has to be "rw".

    How about using the initializer option?

    use strict; use warnings; use 5.012; package Dog; use Moose; has color => ( is => "ro", initializer => sub { my ($self, $attr_val, $setter) = @_; defined $attr_val or $setter->('black'); } ); my $dog = Dog->new(color => undef); say $dog->color; --output:-- black

    I am not so much interested in the feature itself, but it is interesting to see some examples of Moose-metaprogramming at work...

    Never mind.

      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.

      For completeness, here's everything in one file.

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

        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.