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

Respected monks,

May be I am getting confused and/or doing something stupid, but please help me understand this.

I am trying to change the array content. So I wrote the following.

use strict; use warnings; my @arr = (1..10); sub test_this { print "Inside the sub, before the map \@arr: @arr\n"; print "Inside the sub, before the map \@\_ = @_\n"; @arr = map { 2 * $_ } @_; print "Inside the sub, after the map \@arr: @arr\n"; print "Inside the sub, after the map \@\_ = @_\n"; } print "Before calling the sub test_this \@arr: @arr\n"; &test_this(@arr); print "After calling the sub test_this \@arr: @arr\n";

But it seems to alter the @_ as well after the map. Here's the output.

pritesh@t430:~$ perl test.pl Before calling the sub test_this @arr: 1 2 3 4 5 6 7 8 9 10 Inside the sub, before the map @arr: 1 2 3 4 5 6 7 8 9 10 Inside the sub, before the map @_ = 1 2 3 4 5 6 7 8 9 10 Inside the sub, after the map @arr: 2 4 6 8 10 12 14 16 18 20 Inside the sub, after the map @_ = 2 4 6 8 10 12 14 16 18 20 After calling the sub test_this @arr: 2 4 6 8 10 12 14 16 18 20 pritesh@t430:~$

I was not expecting the @_ to change after the map and was expecting Inside the sub, after the map @_ =  1 2 3 4 5 6 7 8 9 10 but what shows is  Inside the sub, after the map @_ = 2 4 6 8 10 12 14 16 18 20

First I thought @_ is a copy of whatever I pass to the sub. But that's not the case.

What am I missing?

Replies are listed 'Best First'.
Re: How come @_ gets changed here?
by tobyink (Canon) on Jun 10, 2017 at 10:36 UTC

    No, @_ is an alias to the sub's arguments. That's how things like chomp work:

    sub my_chomp { $_[0] =~ s/\r?\n\z//sm; } my $var = "foo\n"; my_chomp $var; print "[[$foo]]\n";

    perlsub says:

    The array @_ is a local array, but its elements are aliases for the actual scalar parameters.

Re: How come @_ gets changed here?
by LanX (Saint) on Jun 10, 2017 at 11:10 UTC
    That is doubly confusing code ...

    2 effects are overlaying.

    • the @arr inside the sub is the same like outside, because you didn't use my again. (Change the name and strict will throw an error, otherwise the function is a closure)
    • the elements of @_ are aliases of the elements passed, i.e. the values of @arr (like demonstrated by Tobyink)

    Since you reassign these elements with the map, you are effectively changing @_, it's like writing @_ = map { 2 * $_ } @_;

    Change this to my @arr = map { 2 * $_ } @_; and your problem disappears

    Style tips how to avoid this

  • it's always a good strategy to choose meaningful names for variables instead of arr or array , like this strict would have caught the error.
  • don't declare variables on top of your program if you only intend to use them much later.

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

Re: How come @_ gets changed here?
by kcott (Archbishop) on Jun 10, 2017 at 18:55 UTC

    G'day pritesh_ugrankar,

    Whenever I see code like

    &function(...)

    it raises a red flag.

    Unless you can expand that to

    # In the following code, I am aware of the leading ampersand. # I have read, and fully understood, all the implications of # writing the code, as is; having read and completed digested # all information in http://perldoc.perl.org/perlsub.html. # I totally intended that code to be written as is because # ... # ... # ... &function(...)

    To understand my code better, refer to "perlsub - Perl subroutines".

    Unless you can confidently fill in all of the blanks in the above comments, you've almost certainly done something wrong.

    — Ken

      Not sure what you mean here. Are you complaining about the use of the ampersand with the function call? I usually advise against that too, but in this case the behavior is the same with or without it.

        You raise a valid point and I'm definitely not "complaining" about anything. Like you, I "usually advise against" that usage.

        My intention was to point out, quite unequivocally, that there was a difference between

        &sub(@args)

        and

        sub(@args)

        Reviewing what I originally wrote, it's perhaps not immediately obvious that, without a valid reason for writing

        &sub(@args)

        it would generally be preferable to write

        sub(@args)

        Thankyou for raising this point and providing an opportunity for clarification.

        — Ken

Re: How come @_ gets changed here?
by ikegami (Patriarch) on Jun 11, 2017 at 02:21 UTC

    Perl arguments are passed by reference. Change them in the sub, and they'll change in the caller and vice-versa.

      According to perlsub inside a subroutine the elements of @_ are aliased to the items in the list that are passed to the subroutine. Passing by reference means you pass one or more references in the arguments to the subroutine. Then elements of @_ are aliased to the references and you can make local copies inside the subroutine.

        "Pass By Reference" (vs. "Pass By Value") is a general computer science term which is implemented as "aliasing" in Perl.

        Perl's "references" are totally different.

        update

        Context matters: Canadians are North Americans proud not to be Americans. ;)

        update

        OK, I consider this perlsub#Pass-by-Reference a documentation bug.

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

Re: How come @_ gets changed here?
by Anonymous Monk on Jun 10, 2017 at 14:56 UTC
    The behavior of this script in perl 5.26 is so weird that it seems like it must be a bug.
    Before calling the sub test_this @arr: 1 2 3 4 5 6 7 8 9 10 Inside the sub, before the map @arr: 1 2 3 4 5 6 7 8 9 10 Inside the sub, before the map @_ = 1 2 3 4 5 6 7 8 9 10 Inside the sub, after the map @arr: 2 4 6 8 10 12 14 16 18 20 Use of uninitialized value $_[0] in join or string at foo line 11. Use of uninitialized value $_[3] in join or string at foo line 11. Use of uninitialized value $_[6] in join or string at foo line 11. Use of uninitialized value $_[9] in join or string at foo line 11. Inside the sub, after the map @_ = _ Use of uninitialized value $_[0] + in join or string at foo line 11. _ Use of uninitialized value $_[3] in join or string at foo line 11. _ Use of uninitialized value $_[6] in join or string at foo line 11. After calling the sub test_this @arr: 2 4 6 8 10 12 14 16 18 20
    If I throw in print Dumper(@_), I get this:
    panic: attempt to copy freed scalar 7fe03e8040b0 to 7fe03e82c858 at /o +pt/local/lib/perl5/5.26/darwin-thread-multi-2level/Data/Dumper.pm lin +e 602.
    Anyone know what's going on?

      Interesting, a bisect with a test program something like this:

      my @a=(3); sub x { @a = map {$_*2} @_; "@_" eq "6" or die } &x(@a);

      says that the first bad commit is a5f4850559, which makes v5.24.0, or more specifically v5.23.2, the first version with this behavior. Since that commit is heavy on the internals and I'm not an expert on that, I can't say more at the moment, although it does seem like a regression to me.

        It's not particularly a regression. Perl suffers from a particular over-arching bug, which is hard to fix (which is why it hasn't been fixed yet): elements on perl's execution stack are not reference counted. This means that if you empty something like an array whose elements are on the stack (and will have been aliased to elements of @_ if this is a function call), and if nothing else holds a reference count to those elements, then they will be prematurely freed (and possibly re-allocated) while still accessible.

        That commit merely causes that bug to show up where previously it happened to be masked. For example in 5.22.0 with this code:

        my @a=(3,4); sub x { @a = (); print "[$_]" for @_; print "\n"; } x(@a);
        you get:
        $ perl5220 -w ~/tmp/p Use of uninitialized value $_ in concatenation (.) or string at /home/ +davem/tmp/p line 9. Use of uninitialized value $_ in concatenation (.) or string at /home/ +davem/tmp/p line 9. [][]

        Dave.

      I guess they did some optimization to speed up code execution and never expected/tested such a weird case where the same values are at LHS as closed overs and at RHS as aliases.

      The gaps indicate side effects from changing the execution/evaluation order.

      Edit: like not caching a list of the map but iterating and setting each element directly.

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!

Re: How come @_ gets changed here?
by pritesh_ugrankar (Monk) on Jun 10, 2017 at 22:35 UTC

    Respected Monks,

    Thank you for taking time to reply.

    I used & at the beginning of a function because, as per Learning Perl book, I am to use an & before calling the function, else, I might accidentally end up calling a perl built-in. In fact there is a link given in the book https://www.learning-perl.com/2013/05/why-we-teach-the-subroutine-ampersand/ which if I understood right, is stating it's better to use & when one is not sure.

    But from the posts here, I reckon it's not encouraged. So I'm not sure if I should or shouldn't use the &. I will later try to test on other version(s) of Perl and see how it goes.

      In that article, brian d foy is saying that using & sometimes avoids trouble for people who are very new to Perl. What the rest of us are saying is that using & causes more trouble for intermediate-level Perl programmers. Decide for yourself which category you are in, but remember that & is old-perl.

      Mr foy suggests that it is ok to type "&foo;", but never do that, because it actually passes @_ to foo. Always put the parens. "foo();" or "&foo();", but not "&foo;" unless you really mean it.

A reply falls below the community's threshold of quality. You may see it by logging in.