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'
-------------
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,
| [reply] [d/l] |
|
> 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 .
| [reply] [d/l] [select] |
|
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. :)
¹) 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 | [reply] [d/l] [select] |
|
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,
| [reply] [d/l] [select] |
|
|
#!/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.
| [reply] [d/l] |
|
|
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.
| [reply] |
|
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,
| [reply] [d/l] [select] |
|
|
|
|
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";
| [reply] [d/l] |
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..$#_] = ...;
| [reply] [d/l] [select] |
|
|