dynamically add leading underscore to existing sub

_components is a literal key for the $obj hash reference. It is not "coming from" anywhere. This is a quirk of Perl syntax. In the statement

$obj->{_components} = [];

$obj is a reference to a newly created instance of a class (in the prior statement) and _components is being defined as a key in that instance, and being initialized to a reference to an empty array. This is equivalent to

$obj->{'_components'} = [];

For instance

$ perl -de0

Loading DB routines from perl5db.pl version 1.55
Editor support available.

Enter h or 'h h' for help, or 'man perldebug' for more help.

main::(-e:1):   0
  DB<1> $obj->{a} = "hello";

  DB<2> x $obj
0  HASH(0x800756bf8)
   'a' => 'hello'
  DB<3> p $obj->{'a'}
hello
  DB<4> p $obj->{a}
hello
  DB<5>

The _components is just a literal key name that the programmer chose. After that, the call to components can access the key that was just created.

Part of the problem is the interface. The $obj->{_components} deals with very low level details and the $self->components is a level above that. I don't particularly like mixing levels of abstraction like that.

The original code has components doing the job of setting and getting depending on the size of the argument list. Through autovivification, it will initialize $obj->{_components} automatically, so you don't need the $obj->{_components} = [] line except to set up the first call:

sub components {
    my $obj = shift;
    @_ ? push (@{$obj->{_components}}, @_) : @{$obj->{_components}};
}

But, I'd initialize it in components for the case where you call without arguments before you set any components:

sub components {
    my $obj = shift;
    $obj->{_components} = [] unless $obj->{_components};
    @_ ? push (@{$obj->{_components}}, @_) : @{$obj->{_components}};
}

With the postfix deref that's even simpler:

use v5.26;
sub components {
    my $obj = shift;
    $obj->{_components} = [] unless $obj->{_components};
    @_ ? push ( $obj->{_components}->@* , @_) : $obj->{_components}->@*;
}

But I'd change that up. A push returns a number, which probably isn't the interface you want. Get rid of the conditional operator and always return the full list at the end:

use v5.26;
sub components {
    my $obj = shift;
    $obj->{_components} = [] unless $obj->{_components};
    push $obj->{_components}->@* , @_ if @_;
    $obj->{_components}->@*;
}

And, back to your original query: that key is just a string the programmer chose. Often the _ shows up before a key to show that it's metadata or something else that will be ingored. I don't know the intent here though. Instead of typing it out several times, define it once:

use v5.26;
sub components {
    state $key = '_components';
    my $obj = shift;
    $obj->{$key} = [] unless $obj->{$key};
    push $obj->{$key}->@* , @_ if @_;
    $obj->{$key}->@*;
}

Using the experimental signatures feature cleans it up a bit more:

use v5.26;
use experimental qw(signatures);
sub components ($self, @args) {
    state $key = '_components';
    $obj->{$key} = [] unless $obj->{$key};
    push $obj->{$key}->@* , @args if @arg;
    $obj->{$key}->@*;
}

Tags:

Perl