Why does assigning @_ cause sub's parameter to be copied?

J

Jerry Krinock

After several hours of head-scratching, I finally found out why I
changes I made to an array passed by reference to a subroutine are not
seen outside the subroutine, thanks to Steve Litt's Perls of Wisdom
[1]:

"Any change the subroutine performs to @_ or any of its members like
$_[0], $_[1], etc, are changes to the original argument. HOWEVER,
assigning @_ or its elements to other variables makes a separate copy.
Changes to the separate copy are unknown outside of the subroutine."

Maybe if someone could explain the reason why Perl would do this, make
a copy upon *assignment*, it would be easier for me to remember. Is
this a "feature" of the language, or some unfortunate implementation
reality? The reason why I discovered this is that since I'm not a
full-time Perl guy, I try and write readable code, defining lots of
variables. However, as you can see from my code below, my nice and
pedantic subroutine pushBad() doesn't work because it makes a local
assignment, but the hard-to-read pushGood() works.

Thanks for any insights!

Jerry Krinock

1. http://www.troubleshooters.com/codecorn/littperl/perlsub.htm.
Steve apparently knows it well; he repeated that text in six places on
this page :))

#!/usr/bin/perl
use strict ;

my @array = ("start") ;
pushBad(\@array) ;
print("Passed back from pushBad: @array\n") ;
pushGood(\@array) ;
print("Passed back from pushGood: @array\n") ;

sub pushBad {
my $arrayRef = shift ;
my @array = @$arrayRef ;
push (@array, "bad") ;
print("After pushing, in pushBad: @array\n") ;
}

sub pushGood {
push(@{$_[0]}, "good") ;
print("After pushing, in pushGood: @{$_[0]}\n") ;
}

CONSOLE OUTPUT:
After pushing, in pushBad: start bad
Passed back from pushBad: start
After pushing, in pushGood: start good
Passed back from pushGood: start good
 
J

Jürgen Exner

Jerry Krinock said:
"Any change the subroutine performs to @_ or any of its members like
$_[0], $_[1], etc, are changes to the original argument. HOWEVER,
assigning @_ or its elements to other variables makes a separate copy.
Changes to the separate copy are unknown outside of the subroutine."

Maybe if someone could explain the reason why Perl would do this, make
a copy upon *assignment*, it would be easier for me to remember.

Imagine the following code:
@foo = qw 'Original content of foo';
@bar = @foo;
@bar = qw 'New content';
Now, which content do you expect @foo to have?
sub pushBad {
my $arrayRef = shift ;
my @array = @$arrayRef ;
push (@array, "bad") ;
print("After pushing, in pushBad: @array\n") ;
}

And your sample code here is exactly the same as my snippet above except
that instead of an array @foo you got an array @$arrayRef, i.e. one
level of reference. But otherwise they do the same thing: assigning an
array to another array and there is no reason why changing the elements
in the second array should be mirrored in the original array.

jue
 
U

Uri Guttman

JK> 1. http://www.troubleshooters.com/codecorn/littperl/perlsub.htm.
JK> Steve apparently knows it well; he repeated that text in six places on
JK> this page :))

he knows what well? that page is ancient perl style. why are you
learning from it instead of a good book or the perl docs? almost every
perl tutorial on the web is poorly written, inaccurate, buggy and
worse. i have reviewed dozens and they all seem to be similarly bad. the
fact that this page shows &foo style calls is indicative of its low
quality.

I have been unable to hack into a subroutine via its scalar
return. If you know of a way it can be done, please let me know,
as this would be a horrid violation of encapsulation.

what kind of a moronic statement is that? regardless of whether it can
be done or not, it is just dumb to write that.

Returning a List

sub getFnameLname
{
return("Bill", "Clinton");
}

that only returns a list in list context. no use describing sub returns
without covering context as well. typical bad web tute stuff.

Returning a Hash

sub getOfficers
{
return("president"=>"Bill Clinton",
"vice president"=>"Al Gore",
"intern"=>"Monica Lewinsky"
);

that isn't actually returning a hash which can't be done. it is
returning a list (assuming list context as mentioned above) and it may
be assigned to a hash whereby that list will be converted to a hash.

Arguments to a subroutine are accessible inside the subroutine
as list @_. Any change the subroutine performs to @_ or any of
its members like $_[0], $_[1], etc, are changes to the original
argument. HOWEVER, assigning @_ or its elements to other
variables makes a separate copy. Changes to the separate copy
are unknown outside of the subroutine.

what a maroon! the usual idiom is always assigning @_ to
lexicals. instead he starts off with call by reference stuff which is
rarely used. passing real refs is the correct and safe way to do
that. ugh.


JK> #!/usr/bin/perl
JK> use strict ;

JK> my @array = ("start") ;
JK> pushBad(\@array) ;
JK> print("Passed back from pushBad: @array\n") ;
JK> pushGood(\@array) ;
JK> print("Passed back from pushGood: @array\n") ;

JK> sub pushBad {
JK> my $arrayRef = shift ;

that is still the original array ref

JK> my @array = @$arrayRef ;

HERE you copied the array to a lexical. all changes to @array will
remain there.
JK> push (@array, "bad") ;

why would you expect this to change the original array? what possible
way would you think that would happen? you COPIED the array, not a reference.

JK> print("After pushing, in pushBad: @array\n") ;
JK> }



JK> sub pushGood {
JK> push(@{$_[0]}, "good") ;
JK> print("After pushing, in pushGood: @{$_[0]}\n") ;
JK> }

and that is the bad way to pass by reference.

uri
 
F

Frank Seitz

Jerry said:
After several hours of head-scratching, I finally found out why I
changes I made to an array passed by reference to a subroutine are not
seen outside the subroutine, thanks to Steve Litt's Perls of Wisdom
[1]:

"Any change the subroutine performs to @_ or any of its members like
$_[0], $_[1], etc, are changes to the original argument. HOWEVER,
assigning @_ or its elements to other variables makes a separate copy.
Changes to the separate copy are unknown outside of the subroutine."

Maybe if someone could explain the reason why Perl would do this, make
a copy upon *assignment*, it would be easier for me to remember. Is
this a "feature" of the language, or some unfortunate implementation
reality? The reason why I discovered this is that since I'm not a
full-time Perl guy, I try and write readable code, defining lots of
variables. However, as you can see from my code below, my nice and
pedantic subroutine pushBad() doesn't work because it makes a local
assignment, but the hard-to-read pushGood() works.

Thanks for any insights!

Jerry Krinock

1. http://www.troubleshooters.com/codecorn/littperl/perlsub.htm.
Steve apparently knows it well; he repeated that text in six places on
this page :))

#!/usr/bin/perl
use strict ;

my @array = ("start") ;
pushBad(\@array) ;
print("Passed back from pushBad: @array\n") ;
pushGood(\@array) ;
print("Passed back from pushGood: @array\n") ;

sub pushBad {
my $arrayRef = shift ;
my @array = @$arrayRef ;
push (@array, "bad") ;
print("After pushing, in pushBad: @array\n") ;
}

sub pushGood {
push(@{$_[0]}, "good") ;
print("After pushing, in pushGood: @{$_[0]}\n") ;
}

Readable call by reference code in Perl:

sub pushGood {
my $arrayRef = shift;
push(@$arrayRef, "good");
print("After pushing, in pushGood: @$arrayRef\n") ;
}

Frank
--
Dipl.-Inform. Frank Seitz
Anwendungen für Ihr Internet und Intranet
Tel: 04103/180301; Fax: -02; Industriestr. 31, 22880 Wedel

Blog: http://www.fseitz.de/blog
XING-Profil: http://www.xing.com/profile/Frank_Seitz2
 
S

sreservoir

After several hours of head-scratching, I finally found out why I
changes I made to an array passed by reference to a subroutine are not
seen outside the subroutine, thanks to Steve Litt's Perls of Wisdom
[1]:

"Any change the subroutine performs to @_ or any of its members like
$_[0], $_[1], etc, are changes to the original argument. HOWEVER,
assigning @_ or its elements to other variables makes a separate copy.
Changes to the separate copy are unknown outside of the subroutine."

never do this. not unless you have a really good idea of why you're
doing it. you obviously don't.
Maybe if someone could explain the reason why Perl would do this, make
a copy upon *assignment*, it would be easier for me to remember. Is
this a "feature" of the language, or some unfortunate implementation
reality? The reason why I discovered this is that since I'm not a
full-time Perl guy, I try and write readable code, defining lots of
variables. However, as you can see from my code below, my nice and
pedantic subroutine pushBad() doesn't work because it makes a local
assignment, but the hard-to-read pushGood() works.

try this:

push(my @b = @{my $a = [qw/this list/]}, qw/that list/);

what would you expect $a to contain?

it seems you expect [qw/this list that list/].

I prefer, in perls above 5.6, to use subroutine prototypes:

sub pushScalar(\@$) {
my $ref = shift;
push @$ref, shift;
}
 
S

sreservoir

After several hours of head-scratching, I finally found out why I
changes I made to an array passed by reference to a subroutine are not
seen outside the subroutine, thanks to Steve Litt's Perls of Wisdom
[1]:

"Any change the subroutine performs to @_ or any of its members like
$_[0], $_[1], etc, are changes to the original argument. HOWEVER,
assigning @_ or its elements to other variables makes a separate copy.
Changes to the separate copy are unknown outside of the subroutine."

never do this. not unless you have a really good idea of why you're
doing it. you obviously don't.
Maybe if someone could explain the reason why Perl would do this, make
a copy upon *assignment*, it would be easier for me to remember. Is
this a "feature" of the language, or some unfortunate implementation
reality? The reason why I discovered this is that since I'm not a
full-time Perl guy, I try and write readable code, defining lots of
variables. However, as you can see from my code below, my nice and
pedantic subroutine pushBad() doesn't work because it makes a local
assignment, but the hard-to-read pushGood() works.

try this:

push(my @b = @{my $a = [qw/this list/]}, qw/that list/);

what would you expect $a to contain?

it seems you expect [qw/this list that list/].

I prefer, in perls above 5.6, to use subroutine prototypes:

sub pushScalar(\@$) {
my $ref = shift;
push @$ref, shift;
}

called pushScalar(@array, scalar)
 
J

Jerry Krinock

Thanks for all the replies. Upon further testing, I see that
dereferencing works the same way in "C" language functions.

Retrieving $arrayRef = shift, and then referring to @$arrayRef, to
avoid making a copy, works as expected, is efficient, and is readable.

Jerry
 
U

Uri Guttman

JK> Thanks for all the replies. Upon further testing, I see that
JK> dereferencing works the same way in "C" language functions.

no it doesn't. there are many difference between perl's references and
c's pointers. if you don't learn the differences you will not appreciate
the power of references. refs can't be modified (c's pointers can be) so
they are always safe - no core dumps are possible via normal ref
use. refs can only be dereferenced into their original type. c's
pointers can be coerced to anything and abused. user code can't create a
ref to whatever. c's pointers can be assigned to point to anywhere.

JK> Retrieving $arrayRef = shift, and then referring to @$arrayRef, to
JK> avoid making a copy, works as expected, is efficient, and is readable.

and it took you this long to learn this? as i said, you picked the wrong
source to learn perl (hell, pretty much no web source is good for
learning perl). the perl doc perlreftut would have taught you this and
more in less time and with less pain. then you could move on to perlre
(the reference reference doc), perllol and perldsc. no web needed as
these all come with your perl installation.

uri
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top