Why is it "very bad practice" to override new when using Moose?

808 views Asked by At

From the Moose::Manual::BestPractices page:

Overriding new is a very bad practice. Instead, you should use a BUILD or BUILDARGS methods to do the same thing. When you override new, Moose can no longer inline a constructor when your class is immutabilized.

My question is why is this considered very bad practice?

I thought an "inline" constructor just meant that the constructor was defined in the same package as the class. If this is true wouldn't it mean that if you overrode new within that package the constructor would still be considered an "inline" constructor? Please correct me if I'm wrong. I don't fully understand the concept of what it means for a constructor to be "inline."


The reason I came across this question is because I'm creating a script that builds a list of objects. If a user attempts to create a new object that is identical to one in the list I want to stop Moose from creating a new object and just return a reference to the existing one.

When a new object is created I want to push it onto the list and return the new object. When an attempt is made to create existing object the existing object should be returned and not pushed onto the list.

# Some pseudo code showing the logic of what I attempted
around BUILDARGS => sub {
    my ($orig, $self, %args) = @_;

    # loop through objects in list
    for my $object (@list) {

        # if $args used to define the new object
        # match the arguments of any object in the list
        # return the matched object

        # I want this to directly return the object
        # and skip the call to BUILD
    }

    return $self->orig(
        # Insert args here
    );
};

sub BUILD {
    my ($self) = @_;

    # I don't want this call to happen if the object already existed
    push @list, $self;
}   

When creating a new object I attempted to use BUILD to push it onto the list once it is created. The problem is when I attempted to create an existing object and use BUILDARGS to return the existing object doesn't seem to stop Moose from calling BUILD which attempts to push the object onto the list.

The only way I was able to get around this was to override new and have it return the existing object without creating a new one.

# Some pseudo code showing the overridden constructor
sub new {
    my ($class, %args) = @_;

    # loop through objects in list
    for my $object (@list) {

        # if $args used to define the new object
        # match the arguments of any object in the list
        # return the matched object
    }

    # Build the object
    my $self = bless {
        # Insert args here
    }, $class;

    # Add the object to the list
    push @list, $object;
}

Overriding new worked, but if it really is such a horrible idea, as the Moose documentation seems to suggest, is there a better way to do it?

2

There are 2 answers

0
tjwrona On BEST ANSWER

I found another issue with overriding new, other than Moose not being able to in-line the constructor. I discovered that if you override new, it prevents Moose from being able to make attributes "required" when calling the constructor.


For example if you override new and define a class with an attribute like so:

has 'var' => (is => 'rw', isa => 'Str', => required => 1);

Moose will allow you to create a new instance of this class without passing it a value for var.


I found a workable solution for what I want to do, at least I think it's workable. I don't really need the speed of an in-line constructor (I'm not creating millions of objects), but I do need the ability to "require" attributes. If you use the around function provided by Moose you can essentially override new, without preventing Moose from being able to "require" attributes.

ex:

around new => sub {
    my ($orig, $class, %args) = @_;

    # loop through objects in list
    for my $object (@list) {

        # if $args used to define the new object
        # match the arguments of any object in the list
        # return the matched object without calling new
    }

    # Create a new object
    my $self = $class->orig(%args);

    # Add it to the list of objects.
    push @list, $self;
};

This approach still gives you the warning about not being able to create an in-line constructor, but at least it will function correctly.

Note: adding __PACKAGE__->meta->make_immutable('inline_constructor' => 0); to the end of your package will suppress this warning.

7
Borodin On

In-lining a subroutine means to copy its code at the point of the call instead of inserting a subroutine call. This is much faster because it avoids the overhead of collecting the parameters and any local variables on the stack as well as the the call and return operations. It is impossible if the class hasn't been declared immutable because any mutation may mean that the constructor has to change and so no longer can be inserted in-line