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

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

from perlsub, The array @_ is a local array, but its elements are aliases for the actual scalar parameters. In particular, if an element $_[0] is updated, the corresponding argument is updated, but below code block output are not expected, why?

sub swap_1 { (@_) = reverse(@_); } sub swap_2 { my @item = reverse(@_); (@_) = ($item[0], $item[1]); print "##swap2_1: $item[0]\n"; print "##swap2_1: $item[1]\n"; print "-------------\n"; $_[0] = $item[0]; $_[1] = $item[1]; print "##swap2_2: $_[0]\n"; print "##swap2_2: $_[1]\n"; print "-------------\n"; } sub swap_3 { $_[0] = "AAA"; $_[1] = "BBB"; } my $one = "I am one"; my $two = "I am two"; swap_1($one,$two); print "one is '$one'\n"; print "two is '$two'\n"; print "-------------\n"; swap_2($one,$two); print "one is '$one'\n"; print "two is '$two'\n"; print "-------------\n"; swap_3($one,$two); print "one is '$one'\n"; print "two is '$two'\n"; print "-------------\n";

I expect swap_1, swap_2 can do the swap work, but obviously, I am wrong. but swap_3 is expected

below are the output

one is 'I am one' two is 'I am two' ------------- ##swap2_1: I am two ##swap2_1: I am one ------------- ##swap2_2: I am two ##swap2_2: I am one ------------- one is 'I am one' two is 'I am two' ------------- one is 'AAA' two is 'BBB' -------------

Replies are listed 'Best First'.
Re: Accessing Arguments inside Subroutines via @_
by Athanasius (Archbishop) on Mar 20, 2015 at 02:53 UTC

    Hello citi2015, and welcome to the Monastery!

    The final sentence of the paragraph in perlsub you quote is this:

    Assigning to the whole array @_ removes that aliasing, and does not update any arguments.

    This was new to me. It answers your question, but I would be interested to know what motivated the design decision behind it.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      > I would be interested to know what motivated the design decision behind it.

      I think it follows the normal inner mechanism.

      The elements are aliases not the array holding them.

      Think about how shift @_ deletes an alias.

      One might argue that the array should not be writable, but its sometimes necessary to rewrite @_ before doing a goto &sub .

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)

      PS: Je suis Charlie!

      I think what is really troubling you is the ambiguity between setting a symbol's slot and assigning to a symbol in Perl. (not sure if this is proper terminology)

      Pure Perl has neither an explicit alias operator, nor an explicit unalias operator.²

      So if a scalar variable $a is an alias to $b you can't easily say $a=42 without changing $b , i.e. replacing the alias in the $a -"slot" with a literal 42.¹

      But when operating with arrays like @_ you have the choice.

      $_[0]=42 is accessing the alias behind, but @_=(42) will replace the content of the slot.

      DB<106> sub repl { @_=(42); print "> $_[0]"; } DB<107> $a=666 => 666 DB<108> repl $a > 42 DB<109> $a => 666

      It has already been shown how to access multiple aliases at once (slicing), similarly you can use splice to access multiple slots of an array at once.

      I hope the distinction between setting a slot and assigning to an alias is clearer now. :)

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)

      PS: Je suis Charlie!

      ¹) But you can always use my $a=42 in a new scope or local $a=42 if it's a package var.

      ²) like tie and untie

        Hello LanX,

        I think what is really troubling you is the ambiguity between setting a symbol's slot and assigning to a symbol in Perl.

        I’m not sure if I understand the distinction you’re making here, but my current understanding of a Perl alias is that it’s in some ways analogous to a smart pointer. That is, it has its own identity as a scalar value (of type “alias”), but under certain conditions it behaves “transparently” as the entity it aliases, like a dereferenced pointer. To explain what I mean: I was experimenting with various permutations of the following code:

        and the output shows that following unshift @_ the element $_[1] is now an alias for the first argument. So, when explaining how @_ functions in a subroutine, I would no longer say “$_[0] is an alias for the first argument,” but rather “$_[0] is initialised to a (scalar value which is an) alias for the first argument.”

        And the difference in behaviour between, e.g., @_ = reverse @_ and ( $_[0], $_[1] ) = ( $_[1], $_[0] ), comes down to whether the aliases act “transparently” or ”opaquely.” When elements of @_ are accessed individually — whether singly or as part of an array slice — they behave “transparently,” but when the array @_ is assigned-to — via = or splice — its alias values are accessed “opaquely,” meaning their referents remain unaffected.

        Well, that’s my current understanding, and it may be just a convoluted way of repeating your explanation in different words. Anyway, I think I now understand what’s going on a little better than I did before. :-)

        Update: Fixed typo.

        Thanks for the help,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

        Pure Perl has neither an explicit alias operator

        You can (ab)use typeglobs for this, at least for package variables. For instance:

        #!/usr/bin/perl no strict; use warnings; use feature qw/say/; $a = 42; *b = *a; say $b; # 42 $b = 69; say $a; # 69

        I can't think of a way of doing this with lexicals off the top of my head, though.

        nor an explicit unalias operator.²

        So if a scalar variable $a is an alias to $b you can't easily say $a=42 without changing $b , i.e. replacing the alias in the $a -"slot" with a literal 42.¹

        I don't think this is needed. Suppose that you have variables - let's say lexicals - $a and $b, with the latter being aliased to the former. Unalias $a would mean creating a new lexical that's not related to $b anymore, so why not just declare a new lexical with the same value (my $c = $b) and use that instead? The result's the same, and it's arguably clearer, since if you look at any line in isolation there's no confusion over whether $a is (still) aliased to $b anymore.

        OTOH there's something to be said in favor of explicit alias and unalias operators as well. I'd not be surprised at all if there were CPAN modules that implemented this.

      Thanks a lot, Athanasius.

      No design decision behind it. A newer read that code from one book and find this error, I just realise I cannot explain. so I do my test and post the question on the community.

      Thanks again for the help.

        Hello citi2015,

        I meant Perl’s design decision to remove the aliasing when @_ is assigned-to within a subroutine.

        You can, of course, do the swap by explicitly accessing the subroutine arguments as individual elements of @_:

        sub swap { my $temp = $_[0]; $_[0] = $_[1]; $_[1] = $temp; }

        — but note the necessity of “remembering” the initial value of $_[0] by storing it in a temporary variable. And I think that answers my question: if the aliasing were not removed, @_ = reverse @_ would produce wrong results, because some elements would be changed (assigned-to) before they were assigned-from.

        Hope that helps,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: Accessing Arguments inside Subroutines via @_
by oiskuu (Hermit) on Mar 20, 2015 at 19:51 UTC

    As a workaround, you can assign to an array slice.

    sub rev { @_[0 .. $#_] = reverse @_ } my @test = qw( a b c d ); print "@test\n"; rev @test; print "@test\n";

Re: Accessing Arguments inside Subroutines via @_
by ikegami (Patriarch) on Mar 23, 2015 at 01:54 UTC
    @_ = ( $item[0], $item[1] );
    is effectively
    @_ = (); push @_, $item[0], $item[1];
    You're not assigning to the alias but replacing them with new scalars. To assign to the aliases, you'll have to list the elements or use an array slice.
    ( $_[0], $_[1] ) = ...;
    or
    @_[0..$#_] = ...;