Reputation: 195
I am learning perl and understand that it is a common and accepted practice to unpack subroutine arguments using shift. I also understand that it is common and acceptable practice to omit function arguments to use the default @_
array.
Considering these two things, if you call a subroutine without arguments, the @_
can (and will, if using shift) be changed. Does this mean that calling another subroutine with default arguments, or, in fact, using the @_
array after this, is considered bad practice? Consider this example:
sub total { # calculate sum of all arguments
my $running_sum;
# take arguments one by one and sum them together
while (@_) {
$running_sum += shift;
}
$running_sum;
}
sub avg { calculate the mean of given arguments
if (@_ == 0) { return }
my $sum = &total; # gets the correct answer, but changes @_
$sum / @_ # causes division by zero, since @_ is now empty
}
My gut feeling tells me that using shift to unpack arguments would actually be bad practice, unless your subroutine is actually supposed to change the passed arguments, but I have read in multiple places, including Stack Overflow, that this is not a bad practice.
So the question is: if using shift is common practice, should I always assume the passed argument list could get changed, as a side-effect of the subroutine (like the &total
subroutine in the quoted example)? Is there maybe a way to pass arguments by value, so I can be sure that the argument list does not get changed, so I could use it again (like in the &avg
subroutine in the quoted text)?
Upvotes: 17
Views: 2059
Reputation: 98398
You should avoid using the &func;
style of calling unless you have a really good reason, and trust that others do the same.
To guard your @_
against modification by a callee, just do &func()
or func
.
Upvotes: 3
Reputation: 57640
In general, shift
ing from the arguments is ok—using the &
sigil to call functions isn't. (Except in some very specific situations you'll probably never encounter.)
Your code could be re-written, so that total
doesn't shift
from @_
. Using a for-loop may even be more efficient.
sub total {
my $total = 0;
$total += $_ for @_;
$total;
}
Or you could use the sum
function from List::Util
:
use List::Util qw(sum);
sub avg { @_ ? sum(@_) / @_ : 0 }
Using shift
isn't that common, except for extracting $self
in object oriented Perl. But as you always call your functions like foo( ... )
, it doesn't matter if foo
shift
s or doesn't shift
the argument array.
(The only thing worth noting about a function is whether it assigns to elements in @_
, as these are aliases for the variables you gave as arguments. Assigning to elements in @_
is usually bad.)
Even if you can't change the implementation of total
, calling the sub with an explicit argument list is safe, as the argument list is a copy of the array:
(a) &total
— calls total
with the identical @_
, and overrides prototypes.
(b) total(@_)
— calls total
with a copy of @_
.
(c) &total(@_)
— calls total
with a copy of @_
, and overrides prototypes.
Form (b) is standard. Form (c) shouldn't be seen, except in very few cases for subs inside the same package where the sub has a prototype (and don't use prototypes), and they have to be overridden for some obscure reason. A testament to poor design.
Form (a) is only sensible for tail calls (@_ = (...); goto &foo
) or other forms of optimization (and premature optimization is the root of all evil).
Upvotes: 20
Reputation: 20280
Here are some examples where the careful use of @_
matters.
1. Hash-y Arguments
Sometimes you want to write a function which can take a list of key-value pairs, but one is the most common use and you want that to be available without needing a key. For example
sub get_temp {
my $location = @_ % 2 ? shift : undef;
my %options = @_;
$location ||= $options{location};
...
}
So now if you call the function with an odd number of arguments, the first is location. This allows get_temp('Chicago')
or get_temp('New York', unit => 'C')
or even get_temp( unit => 'K', location => 'Nome, Ak')
. This may be a more convenient API for your users. By shifting the odd argument, now @_
is an even list and may be assigned to a hash.
2. Dispatching
Lets say we have a class that we want to be able to dispatch methods by name (possibly AUTOLOAD could be useful, we will hand roll). Perhaps this is a command line script where arguments are methods. In this case we define two dispatch methods one "clean" and one "dirty". If we call with the -c
flag we get the clean one. These methods find the method by name and call it. The difference is how. The dirty one leaves itself in the stack trace, the clean one has to be more cleaver, but dispatches without being in the stack trace. We make a death
method which gives us that trace.
#!/usr/bin/env perl
use strict;
use warnings;
package Unusual;
use Carp;
sub new {
my $class = shift;
return bless { @_ }, $class;
}
sub dispatch_dirty {
my $self = shift;
my $name = shift;
my $method = $self->can($name) or confess "No method named $name";
$self->$method(@_);
}
sub dispatch_clean {
my $self = shift;
my $name = shift;
my $method = $self->can($name) or confess "No method named $name";
unshift @_, $self;
goto $method;
}
sub death {
my ($self, $message) = @_;
$message ||= 'died';
confess "$self->{name}: $message";
}
package main;
use Getopt::Long;
GetOptions
'clean' => \my $clean,
'name=s' => \(my $name = 'Robot');
my $obj = Unusual->new(name => $name);
if ($clean) {
$obj->dispatch_clean(@ARGV);
} else {
$obj->dispatch_dirty(@ARGV);
}
So now if we call ./test.pl
to invoke the death method
$ ./test.pl death Goodbye
Robot: Goodbye at ./test.pl line 32
Unusual::death('Unusual=HASH(0xa0f7188)', 'Goodbye') called at ./test.pl line 19
Unusual::dispatch_dirty('Unusual=HASH(0xa0f7188)', 'death', 'Goodbye') called at ./test.pl line 46
but wee see dispatch_dirty
in the trace. If instead we call ./test.pl -c
we now use the clean dispatcher and get
$ ./test.pl -c death Adios
Robot: Adios at ./test.pl line 33
Unusual::death('Unusual=HASH(0x9427188)', 'Adios') called at ./test.pl line 44
The key here is the goto
(not the evil goto) which takes the subroutine reference and immediately switches the execution to that reference, using the current @_
. This is why I have to unshift @_, $self
so that the invocant is ready for the new method.
Upvotes: 2
Reputation: 5001
I found the following gem in http://perldoc.perl.org/perlsub.html:
"Yes, there are still unresolved issues having to do with visibility of @_ . I'm ignoring that question for the moment. (But note that if we make @_ lexically scoped, those anonymous subroutines can act like closures... (Gee, is this sounding a little Lispish? (Never mind.)))"
You might have run into one of those issues :-(
OTOH amon is probably right -> +1
Upvotes: 0
Reputation: 1342
I tried a simple example:
#!/usr/bin/perl
use strict;
sub total {
my $sum = 0;
while(@_) {
$sum = $sum + shift;
}
return $sum;
}
sub total1 {
my ($a, $aa, $aaa) = @_;
return ($a + $aa + $aaa);
}
my $s;
$s = total(10, 20, 30);
print $s;
$s = total1(10, 20, 30);
print "\n$s";
Both print statements gave answer as 60.
But personally I feel, the arguments should be accepted in this manner:
my (arguments, @garb) = @_;
in order to avoid any sort of issue latter.
Upvotes: 0
Reputation: 3692
Refs:
sub refWay{
my ($refToArray,$secondParam,$thirdParam) = @_;
#work here
}
refWay(\@array, 'a','b');
HashWay:
sub hashWay{
my $refToHash = shift; #(if pass ref to hash)
#and i know, that:
return undef unless exists $refToHash->{'user'};
return undef unless exists $refToHash->{'password'};
#or the same in loop:
for (qw(user password etc)){
return undef unless exists $refToHash->{$_};
}
}
hashWay({'user'=>YourName, 'password'=>YourPassword});
Upvotes: 0
Reputation: 2643
Perl is a little too lax sometimes and having multiple ways of accessing input parameters can make smelly and inconsistent code. For want of a better answer, try to impose your own standard.
Here's a few ways I've used and seen
Shifty
sub login
{
my $user = shift;
my $passphrase = shift;
# Validate authentication
return 0;
}
Expanding @_
sub login
{
my ($user, $passphrase) = @_;
# Validate authentication
return 0;
}
Explicit indexing
sub login
{
my user = $_[0];
my user = $_[1];
# Validate authentication
return 0;
}
Enforce parameters with function prototypes (this is not popular however)
sub login($$)
{
my ($user, $passphrase) = @_;
# Validate authentication
return 0;
}
Sadly you still have to perform your own convoluted input validation/taint checking, ie:
return unless defined $user;
return unless defined $passphrase;
or better still, a little more informative
unless (defined($user) && defined($passphrase)) {
carp "Input error: user or passphrase not defined";
return -1;
}
Perldoc perlsub should really be your first port of call.
Hope this helps!
Upvotes: 2