Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moo object extends order

Tags:

perl

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"
]
like image 974
SparkeyG Avatar asked Jan 26 '23 15:01

SparkeyG


1 Answers

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();
like image 200
Joel Berger Avatar answered Feb 04 '23 21:02

Joel Berger