SparkeyG
SparkeyG

Reputation: 532

Moo object extends order

Give the below code, it appears that the order you instatiate the objects matter. The below code will print the same list for both objects when i would expect a different list for each because list is an instance attribute that is created at BUILD time.

package t;

use Moo;
use Types::Standard qw(ArrayRef);

my @list = qw/foo bar baz/;

has list => (
    is => 'rw',
    isa => ArrayRef,
    default => sub {\@list}
);

1;
---
package u;

use Moo;
use Types::Standard qw(ArrayRef);
extends 't';

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

    push @{$self->list()}, qw/apple banana/;
    return $self;
}
1;
---
#!perl

use Data::Printer;
use t;
use u;

my $u = u->new();
p $u->list();

my $t = t->new();
p $t->list();

Current Output:

\ [
    [0] "foo",
    [1] "bar",
    [2] "baz",
    [3] "apple",
    [4] "banana"
]
\ [
    [0] "foo",
    [1] "bar",
    [2] "baz",
    [3] "apple",
    [4] "banana"
]

Expected output:

\ [
    [0] "foo",
    [1] "bar",
    [2] "baz",
    [3] "apple",
    [4] "banana"
]
\ [
    [0] "foo",
    [1] "bar",
    [2] "baz"
]

Upvotes: 4

Views: 186

Answers (1)

Joel Berger
Joel Berger

Reputation: 20280

Since you mutate the array in question, you don't want a reference to the array that you use as the default \@list, you want to take a shallow copy [@list].

package t;

use Moo;
use Types::Standard qw(ArrayRef);

my @list = qw/foo bar baz/;

has list => (
    is => 'rw',
    isa => ArrayRef,
        builder =>
    default => sub { [@list] }
);

package u;

use Moo;
use Types::Standard qw(ArrayRef);
extends 't';

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

    push @{$self->list()}, qw/apple banana/;
    return $self;
}

package main;

use Data::Printer;

my $u = u->new();
p $u->list();

my $t = t->new();
p $t->list();

While I'm at it, using BUILD to modify an attribute is possible but not necessarily the best. You can use something like a lazy attribute with a builder method, then overload that method in the subclass, ala

package t;

use Moo;
use Types::Standard qw(ArrayRef);

my @list = qw/foo bar baz/;

has list => (
    is => 'rw',
    isa => ArrayRef,
    builder => '_build_list',
    lazy => 1,
);

sub _build_list {
  my $self = shift;
  return [@list];
}

package u;

use Moo;
extends 't';

sub _build_list {
  my $self = shift;
  my $list = $self->SUPER::_build_list();
  push @$list, qw/apple banana/;
  return $list;
}

package main;

use Data::Printer;

my $u = u->new();
p $u->list();

my $t = t->new();
p $t->list();

Upvotes: 8

Related Questions