http://qs1969.pair.com?node_id=671135

why_bird has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

Me again.. this time I'm having a problem with changing values in a subroutine. When a certain event occurs (my data returns '0' or 'inf') I need to print some things to a file (that's fine) and then clear the values of some other variables, to start again.

The array of data is determined by the main body of the program and is normally reset at the end of the while loop, and the counter counts from (say) 1 - 10 during the loop and again needs to be reset at the end of the loop.

My variables are locally scoped, so if I try changing them directly in the subroutine, nothing happens: (as expected)

sub subNext { ... ## print some stuff my $print_=$_[0] print FILE $print_; ... $count=0; @data=(); next; } while ( some condition ){ ... ## some stuff (subNext breaks to the next instance of the loop) ... my @data; ## process to fill @data with data my @count; ## count increments in various places during the loop during the loop if ($some_var == 0){ &subNext($print); } ... ## rest of code ... }

So then I tried passing by reference:

sub subNext { ... ## print some stuff $print_=$_[0] print FILE $print_; ... print "count is: $$_[2] , reference is: $_[2] \n"; print "data is: @$_[1] , reference is: $_[1] \n"; $_[2]=0; $_[1]=(); next; } while ( some condition ){ ... ## some stuff (subNext breaks to the next instance of the loop) ... my @data; ## process to fill @data with data my @count; ## count increments in various places during the loop if ($some_var == 0){ &subNext($print, \@data, \$count); } ... ## rest of code ... }

That prints
count is: , reference is: SCALAR(0x5147a0)
data is: , reference is: ARRAY(0x5146b0)
And netiher the counter nor the data array are reset. Can anyone help me understand what I'm doing wrong?

Thanks!

Replies are listed 'Best First'.
Re: Changing local variables in subroutine by passing by reference?
by moritz (Cardinal) on Feb 29, 2008 at 10:10 UTC
    You need to dereference your values before assigment:
    sub my_sub { my $scalar_ref = $_[0]; # note the $$: $$scalar_ref = 'new value'; } # and call it: my_sub(\$variable); # another way, since @_ is an alias: sub my_sub_2 { $_[0] = 3; } my $a = 1; my_sub_2($a); print $a; # prints 3

    But generally it is better style to modify your values on the caller side, based on the return value of the function.

      Thanks, I think that's working now :)

      However, I don't think I understand the difference between "# another way, since @_ is an alias:" and what I was doing.. Is it because by assigning a reference to $_[0] (say) $_[0] would then be a reference to a reference? And if so, would my code have worked if I'd added an extra dereferencer (e.g. $$$_[0] instead of $$_[0]), or is that just nonsense?!

        I don't think I understand the difference between "# another way, since @_ is an alias:"

        In Perl, everything is passed by reference. To avoid confusion with Perl references — an unrelated concept — this is known as "aliasing" in the Perl world.

        When a function is called, the elements of @_ are aliased to the arguments in the caller. Changes to @_ will be reflected in the arguments passed to the function.

        In the following snippet, $_[0] is aliased to $x, and $_[1] is aliased to $y. Notice how changing @_ changed $x and $y, and notice how no references — and no dereferencing — were used.

        sub func { print("f-pre: $_[0], $_[1]\n"); $_[0] = 5; $_[1] = 6; print("f-post: $_[0], $_[1]\n"); } my $x = 3; my $y = 4; print("pre: $x, $y\n"); func($x, $y); print("post: $x, $y\n");
        pre: 3, 4 f-pre: 3, 4 f-post: 5, 6 post: 5, 6
        My second example didn't uses references at all! That's the differences.

        Note that normally your subs are built like this:

        sub my_sub { my ($arg1, $arg2) = @_; ... }

        The assignment to another variable breaks the alias, so if you change $arg1, @_ will not be modified. But if you modify @_ directly,

Re: Changing local variables in subroutine by passing by reference?
by almut (Canon) on Feb 29, 2008 at 10:23 UTC

    You have a precedence problem in your dereferencing ($$_[2] and @$_[1]), i.e. you need additional braces.  And, as moritz said, you need to dereference in the assignment, too. IOW, this should work:

    ... print "count is: ${$_[2]} , reference is: $_[2] \n"; print "data is: @{$_[1]} , reference is: $_[1] \n"; ${$_[2]} = 0; @{$_[1]} = (); ...
      That might answer my question before.. I'll read up on operator precedence.. Thanks :)
      -------------------------------------------------------------
      Those are my principles. If you don't like them, I have others.
      -- Groucho Marx
      --------------------------------------------------------------