在perl中,用默认参数调用多个子程序是不好的做法吗?

时间:2012-12-31 10:50:16

标签: perl subroutine shift

我正在学习perl并且理解使用shift解包子例程参数是一种常见且公认的做法。我也理解,省略函数参数以使用默认的@_数组是常见且可接受的做法。

考虑到这两件事,如果你调用一个没有参数的子程序,@_可以(并且如果使用shift)可以改变。这是否意味着使用默认参数调用另一个子例程,或者事实上,在此之后使用@_数组被认为是不好的做法?考虑这个例子:

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
}

我的直觉告诉我,使用shift来解压缩参数实际上是不好的做法,除非你的子程序实际上应该改变传递的参数,但我已经在多个地方读过,包括Stack Overflow,这不是一个坏事实践。

所以问题是:如果使用shift是常见的做法,我是否总是假设传递的参数列表可能会被更改,作为子例程的副作用(如引用示例中的&total子例程)?有没有办法按值传递参数,所以我可以确定参数列表不会被更改,所以我可以再次使用它(比如在引用文本中的&avg子例程中)?

7 个答案:

答案 0 :(得分:20)

通常,shift来自参数是正确的 - 使用& sigil来调用函数不是。 (除非在一些非常具体的情况下,你可能永远不会遇到。)

您的代码可以重写,因此total不会shift来自@_。使用for循环甚至可能更有效。

sub total {
  my $total = 0;
   $total += $_ for @_;
  $total;
}

或者您可以使用sum中的List::Util功能:

use List::Util qw(sum);

sub avg { @_ ? sum(@_) / @_ : 0 }

除了在面向对象的Perl中提取shift之外,使用$self并不常见。但是,当您始终调用foo( ... )之类的函数时,如果foo shiftshift 无效 {1}}参数数组。
(关于函数唯一值得注意的是它是否分配给@_中的元素,因为它们是您作为参数赋予的变量的别名。分配给@_中的元素通常是不好的。)

即使您无法更改total的实现,使用显式参数列表调用sub也是安全的,因为参数列表是数组的副本:

(a)&total - 使用相同的total调用@_,并覆盖原型。
(b)total(@_) - 使用total的副本调用@_ (c)&total(@_) - 使用total的副本调用@_,并覆盖原型。

表格(b)是标准的。不应该看到形式(c),除非在极少数情况下对于sub具有原型(并且不使用原型)的同一个包内的subs,并且由于某些不明原因它们必须被覆盖。证明设计不佳。 对于尾部调用(@_ = (...); goto &foo)或其他形式的优化(以及过早优化是所有邪恶的根源),表单(a)

答案 1 :(得分:3)

除非你有充分的理由,否则你应该避免使用&func;调用方式,并相信其他人会这样做。

要保护您的@_不被被叫方修改,只需&func()func

答案 2 :(得分:2)

有时候Perl有点过于宽松,并且有多种方式访问​​输入参数可能会使代码变得异常和不一致。如果想要更好的答案,请尝试强加自己的标准。

以下是我使用过的几种方式

位移y

sub login
{
    my $user = shift;
    my $passphrase = shift;
    # Validate authentication    
    return 0;
}

扩展@_

sub login
{
    my ($user, $passphrase) = @_;
     # Validate authentication   
    return 0;
}

显式索引

sub login 
{
    my user = $_[0];
    my user = $_[1];
    # Validate authentication
    return 0;
}

使用函数原型强制执行参数(但这是not popular

sub login($$)
{
    my ($user, $passphrase) = @_;   
    # Validate authentication
    return 0;
}

可悲的是,你仍然需要执行自己的复杂输入验证/污点检查,即:

return unless defined $user;
return unless defined $passphrase;

或者更好的是,提供更多信息

unless (defined($user) && defined($passphrase)) {
    carp "Input error: user or passphrase not defined";
    return -1;
}

Perldoc perlsub应该是您的第一个停靠点。

希望这有帮助!

答案 3 :(得分:2)

以下是谨慎使用@_的一些示例。

<强> 1。哈希参数

有时你想编写一个可以获取键值对列表的函数,但有一个是最常用的,你希望它在不需要键的情况下可用。例如

sub get_temp {
  my $location = @_ % 2 ? shift : undef;
  my %options = @_;
  $location ||= $options{location};
  ...
}

所以现在如果你用奇数个参数调用函数,第一个就是位置。这允许get_temp('Chicago')get_temp('New York', unit => 'C')甚至get_temp( unit => 'K', location => 'Nome, Ak')。这可能是您的用户更方便的API。通过移动奇数参数,现在@_是偶数列表,可以分配给散列。

<强> 2。调度

假设我们有一个类,我们希望能够按名称调度方法(可能AUTOLOAD可能很有用,我们将手动滚动)。也许这是一个命令行脚本,其中参数是方法。在这种情况下,我们定义两个调度方法一个“干净”和一个“脏”。如果我们使用-c标志调用,我们就会得到干净的标志。这些方法按名称查找方法并调用它。不同的是如何。脏的一个离开自己在堆栈跟踪中,干净的一个必须是更多的切割器,但调度没有在堆栈跟踪中。我们制作了death方法,为我们提供了跟踪。

#!/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);
}

现在如果我们调用./test.pl来调用死亡方法

$ ./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

但是我们在跟踪中看到了dispatch_dirty。如果我们调用./test.pl -c,我们现在使用干净的调度程序并获取

$ ./test.pl -c death Adios
Robot: Adios at ./test.pl line 33
    Unusual::death('Unusual=HASH(0x9427188)', 'Adios') called at ./test.pl line 44

这里的关键是goto(不是邪恶的goto),它使用子程序引用并立即使用当前@_将执行切换到该引用。这就是为什么我必须unshift @_, $self以便调用者为新方法做好准备。

答案 4 :(得分:0)

参考文献:

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});

答案 5 :(得分:0)

我尝试了一个简单的例子:

#!/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";

这两个印刷语句的答案都是60。

但我个人认为,这些论点应该以这种方式接受:

 my (arguments, @garb) = @_;

为了避免后者出现任何问题。

答案 6 :(得分:0)

我在http://perldoc.perl.org/perlsub.html中找到了以下宝石:

  

“是的,仍有未解决的问题与@_的可见性有关。我暂时忽略了这个问题。(但请注意,如果我们使@_词法作用域,那些匿名子程序就像闭包一样。 ..(哎呀,听起来有点Lispish?(没关系。)))“

您可能遇到过其中一个问题: - (

OTOH amon可能是对的 - &gt; 1