Reputation: 927
Imagine I have this Perl script
my $name = " foo ";
my $sn = " foosu";
trim($name, \$sn);
print "name: [$name]\n";
print "sn: [$sn]\n";
exit 0;
sub trim{
my $fref_trim = sub{
my ($ref_input) = @_;
${$ref_input} =~ s/^\s+// ;
${$ref_input} =~ s/\s+$// ;
};
foreach my $input (@_){
if (ref($input) eq "SCALAR"){
$fref_trim->($input);
} else {
$fref_trim->(\$input);
}
}
}
Result:
name: [foo]
sn: [foosu]
I would expect $name to be "[ foo ]
" when printing the value after calling trim
, but the sub is setting $name
as I would want it. Why is this working, when it really shouldn't?
I'm not passing $name by reference and the trim
sub is not returning anything. I'd expect the trim
sub to create a copy of the $name
value, process the copy, but then the original $name
would still have the leading and trailing white spaces when printed in the main code.
I assume it is because of the alias with @_
, but shouldn't the foreach my $input (@_)
force the sub to copy the value and only treat the value not the alias?
I know I can simplify this sub and I used it only as an example.
Upvotes: 2
Views: 170
Reputation: 118118
Elements of @_
are aliases to the original variables. What you are observing is the difference between:
sub ltrim {
$_[0] =~ s/^\s+//;
return $_[0];
}
and
sub ltrim {
my ($s) = @_;
$s =~ s/^\s+//;
return $s;
}
Compare your code to:
#!/usr/bin/env perl
my $name = " foo ";
my $sn = " foosu";
trim($name, \$sn);
print "name: [$name]\n";
print "sn: [$sn]\n";
sub trim {
my @args = @_;
my $fref_trim = sub{
my ($ref_input) = @_;
${$ref_input} =~ s/^\s+//;
${$ref_input} =~ s/\s+\z//;
};
for my $input (@args) {
if (ref($input) eq "SCALAR") {
$fref_trim->($input);
}
else {
$fref_trim->(\$input);
}
}
}
Output:
$ ./zz.pl
name: [ foo ]
sn: [foosu]
Note also that the loop variable in for my $input ( @array )
does not create a new copy for each element of the array. See perldoc perlsyn
:
The
foreach
loop iterates over a normal list value and sets the scalar variableVAR
to be each element of the list in turn. ......
the
foreach
loop index variable is an implicit alias for each item in the list that you're looping over.
In your case, this would mean that, at each iteration $input
is an alias to the corresponding element of @_
which itself is an alias to the variable that was passed in as an argument to the subroutine.
Making a copy of @_
thus prevents the variables in the calling context from being modified. Of course, you could do something like:
sub trim {
my $fref_trim = sub{
my ($ref_input) = @_;
${$ref_input} =~ s/^\s+//;
${$ref_input} =~ s/\s+\z//;
};
for my $input (@_) {
my $input_copy = $input;
if (ref($input_copy) eq "SCALAR") {
$fref_trim->($input_copy);
}
else {
$fref_trim->(\$input_copy);
}
}
}
but I find making a wholesale copy of @_
once to be clearer and more efficient assuming you do not want to be selective.
Upvotes: 8
Reputation: 85767
I assume it is because of the alias with
@_
, but shouldn't theforeach my $input (@_)
force the sub to copy the value and only treat the value not the alias?
You're right that @_
contains aliases. The part that's missing is that foreach
also aliases the loop variable to the current list element. Quoting perldoc perlsyn
:
If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop. Conversely, if any element of LIST is NOT an lvalue, any attempt to modify that element will fail. In other words, the
foreach
loop index variable is an implicit alias for each item in the list that you're looping over.
So ultimately $input
is an alias for $_[0]
, which is an alias for $name
, which is why you see the changes appearing in $name
.
Upvotes: 2