Search code examples
perlpass-by-reference

Is it possible to increment a scalar reference in Perl?


Say I have a number, $x = 0; and I want to increment it with a subroutine, but the subroutine won't be returning its value:

sub increment {
    my ($var) = @_;

    my @list = (
        'a',
        'b',
        'c',

        ...

        'x',
        'y',
        'z'
    );

    return $list[$var++];
}

while ($x < 10) {
    print increment($x);
}

As-is, this will print aaaaaaaaaa forever instead of abcdefghij. If I replace increment($x) with increment(\$x), it converts the scalar address to a decimal number and increments that instead. In the above scenario, it ends up throwing an error because 25423331 or whatever isn't a valid array element.

If $x were an element in a hash or an array, I could pass the parent as a reference to have the original modified:

$x = {'val' => 0};
while ($x->{'val'} < 10) {
    print increment($x);
}

sub increment {
    ...
    return $list[$var->{$val}++];
}

How can I modify the original value of a scalar reference?


Solution

  • You can pass a reference to the variable to modify.

    sub increment {
        my ($ref) = @_;
        ++$$ref;
    }
    
    my $i = 0;
    say $i;  # prints 0
    increment(\$i);
    say $i;  # prints 1
    

    You could also take advantage of the fact that Perl passes by reference.

    sub increment {
        ++$_[0];
    }
    
    my $i = 0;
    say $i;  # prints 0
    increment($i);
    say $i;  # prints 1
    

    But hiding the increment as such is a really bad idea. Either iterate over a list,

    for my $x ('a'..'z') {
       ...
    }
    

    Or write an iterator.

    sub make_iter {
       my @list = @_;
       return sub {
          return @list ? shift(@list) : ();
       };
    }
    
    my $iter = make_iter('a'..'z');
    while (my ($x) = $iter->()) {
       ...
    }