Moose "builder" vs "default"
Asked Answered
I

3

16

I understand that using builder enables subclasses to override attribute defaults easily and roles can require them. This can also be accomplished using default like so:

has 'foo' =>
    is       => 'rw',
    isa      => 'Str',
    default  => sub { $_[0]->_build_foo };

I'm wondering if there are further advantages to using builder I'm not aware of? I've come up with some myself:

  • builder is declarative so you can introspect that foo is built by _build_foo
  • builder eliminates a subroutine wrapper making it a bit faster
  • builder allows the use of the helpful lazy_build.

UPDATE To clarify, this isn't about default vs builder in general but default => sub { $_[0]->_build_foo } vs builder => '_build_foo'.

Idolize answered 2/3, 2012 at 0:26 Comment(0)
U
13

I think you've already answered your own question. Using builder allows late-binding, which plays nicely with roles and classes that are intended to be subclassed. It's also valuable if the builder is pretty long — I never put a default more than a line long into an attribute definition. There's no real functional difference; default can easily emulate builder, but the result isn't very pretty.

Unnecessary answered 2/3, 2012 at 2:54 Comment(3)
Why do you think default => \&builder doesn't do every one of those things?Adapt
@Adapt because \&builder will be resolved to &builder in the current package. A subclass that provides a new builder would have to do has '+attr' => builder => \&builder again to bind it to the overridden method. OTOH builder => 'builder' is resolved at runtime.Unnecessary
As a counterpoint, a subclass can override the attribute and change the default. This does not require knowing the probably private builder method name. Both require knowing if the attribute is using default or builder because you can't have both. This is generally private information and subject to change. Overriding the default behavior of attributes in Moose is problematic, you have to publish details of how that default behavior is implemented or the subclass has to introspect. :/Idolize
A
4

There's no difference between

default => sub { $_[0]->_build_foo }

and

builder => '_build_foo'

The primary difference between default and builder is that one one calls an anon sub and the other calls a named method.

has created_time_stamp => (
   default => sub { time() },
);

versus

has created_time_stamp => (
   builder => '_build_created_time_stamp',
);

sub _build_created_time_stamp { time() }

Using default reduces scrolling through the code as everything is where you need it. I use it for that reason. That is uses less typing is a bonus.

It also force you to be more explicit about overriding the builder. Other answers have considered this a con, but I consider calling virtual methods on an object that hasn't even been constructed yet to be a bad practice! That's what BUILD is for.

Adapt answered 2/3, 2012 at 2:18 Comment(3)
The question is specifically about default => sub { $_[0]->_build_foo } vs builder => '_build_foo', so subclasses can override the default, not default vs builder in general. That's why the additional sub call.Idolize
@Schwern, Why would you do that?! No reason to do that whatsoever. Well, maybe for consistency if you've used default everywhere else.Adapt
"Why would you do that" is exactly what I'm asking. It seems to me that builder => '_build_foo' is syntax sugar for default => sub { $_[0]->_build_foo }. Is there something more?Idolize
I
4

Using 'builder' and 'default' appropriately can make your code easier to read and organize.

'builder' also can fit a familiar pattern of programming where private methods begin with an underscore.

has json => ( is => 'ro', default => sub { JSON->new } )
has schema => ( is => 'ro', builder => '_schema' }

sub _schema {
  my $self = shift;
  $self->log_debug('constructing schema') if($self->debug);
  My::App::Schema->connect($self->dsn,$self->username,$self->password)  
}

Additionally, using builder allows you to turn expensive functions into memoized accessors without touching the original method:

sub get_things {
  my $self = shift;
  return +{ map { $_ => $self->price_for($_) }
    $self->wodgets->calulate_expensive_things };

Refactor with memoization:

has things => ( is => 'ro', lazy => 1, builder => 'get_things' );

Those are most of the ways I've used builder to clarify my code.

Incandescence answered 2/3, 2012 at 4:13 Comment(2)
I also like to use them for attributes in say, roles, that someone might want to redefine in a class consuming said role. Seems a bit easier than redefining the whole attribute with a '+' in front of the name.Honan
Sorry, this isn't about builder vs default in general but default => sub { $_[0]->_build_foo } vs builder => '_build_foo'.Idolize

© 2022 - 2024 — McMap. All rights reserved.