Existe uma diferença entre a mudança do Perl versus a atribuição de @_ para parâmetros de sub-rotina?

StackOverflow https://stackoverflow.com/questions/415297

  •  03-07-2019
  •  | 
  •  

Pergunta

Vamos ignorar por um momento a melhor prática de Damian Conway de não mais do que três parâmetros posicionais para qualquer sub-rotina.

Existe alguma diferença entre os dois exemplos abaixo em relação ao desempenho ou funcionalidade?

Usando shift:

sub do_something_fantastical {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;
}

Usando @_:

sub do_something_fantastical {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
}

Desde que ambos os exemplos sejam iguais em termos de desempenho e funcionalidade, o que as pessoas pensam de um formato em detrimento do outro?Obviamente o exemplo usando @_ há menos linhas de código, mas não é mais legível usar shift como mostrado no outro exemplo?Opiniões com boa fundamentação são bem-vindas.

Foi útil?

Solução

Há uma diferença funcional. o mudança modifica @_, e a tarefa de @_ não. Se você não precisa usar @_ Depois, essa diferença provavelmente não importa para você. Eu tento sempre usar a atribuição da lista, mas às vezes uso shift.

No entanto, se eu começar com shift, igual a:

 my( $param ) = shift;

Costumo criar este bug:

 my( $param, $other_param ) = shift;

Isso é porque eu não uso shift Isso com frequência, então eu esqueço de ir ao lado direito da tarefa para alterar isso para @_. Esse é o objetivo da melhor prática em não usar shift. Eu poderia fazer linhas separadas para cada shift Como você fez no seu exemplo, mas isso é tedioso.

Outras dicas

Pelo menos em meus sistemas, parece depender da versão do Perl e da arquitetura:

#!/usr/bin/perl -w
use strict;
use warnings;
use autodie;

use Benchmark qw( cmpthese );

print "Using Perl $] under $^O\n\n";

cmpthese(
    -1,
    {
        shifted   => 'call( \&shifted )',
        list_copy => 'call( \&list_copy )',
    }
);

sub call {
    $_[0]->(1..6);  # Call our sub with six dummy args.
}

sub shifted {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;

    return;
}

sub list_copy {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
    return;
}

Resultados:

Using Perl 5.008008 under cygwin

              Rate   shifted list_copy
shifted   492062/s        --      -10%
list_copy 547589/s       11%        --


Using Perl 5.010000 under MSWin32

              Rate list_copy   shifted
list_copy 416767/s        --       -5%
shifted   436906/s        5%        --


Using Perl 5.008008 under MSWin32

              Rate   shifted list_copy
shifted   456435/s        --      -19%
list_copy 563106/s       23%        --

Using Perl 5.008008 under linux

              Rate   shifted list_copy
shifted   330830/s        --      -17%
list_copy 398222/s       20%        --

Portanto, parece que list_copy geralmente é 20% mais rápido que a mudança, exceto no Perl 5.10, onde a mudança é realmente um pouco mais rápida!

Observe que estes foram resultados rapidamente derivados. As diferenças de velocidade reais serão Maior O que está listado aqui, já que o benchmark também conta o tempo necessário para ligar e devolver as sub -rotinas, o que terá um efeito moderador nos resultados. Não fiz nenhuma investigação para ver se a Perl está fazendo algum tipo especial de otimização. Sua milhagem pode variar.

Paulo

Eu imagino que o exemplo do shift seja mais lento do que usar @_ porque são 6 chamadas de função em vez de 1.Se é ou não perceptível ou mesmo mensurável é uma questão diferente.Jogue cada um em um loop de 10 mil iterações e cronometre-as.

Quanto à estética prefiro o método @_.Parece que seria muito fácil bagunçar a ordem das variáveis ​​usando o método shift com um corte e colagem acidental.Além disso, já vi muitas pessoas fazerem algo assim:

sub do_something {
   my $foo = shift;
   $foo .= ".1";

   my $baz = shift;
   $baz .= ".bak";

   my $bar = shift;
   $bar .= ".a";
}

Isso, IMHO, é muito desagradável e pode facilmente levar a erros, por ex.se você cortar o bloco baz e colá-lo sob o bloco de barra.Sou totalmente a favor de definir variáveis ​​perto de onde elas são usadas, mas acho que definir as variáveis ​​passadas no topo da função tem precedência.

Normalmente eu uso a primeira versão. Isso ocorre porque eu geralmente preciso ter erro de verificação de erros junto com os turnos, o que é mais fácil de escrever. Dizer,

sub do_something_fantastical {
    my $foo   = shift || die("must have foo");
    my $bar   = shift || 0;  # $bar is optional
    # ...
}

Prefiro descompactar @_ como uma lista (seu segundo exemplo). Embora, como tudo em Perl, haja casos em que o uso do turno pode ser útil. Por exemplo, os métodos passhru que devem ser substituídos em uma classe base, mas você deseja garantir que as coisas ainda funcionem se não forem substituídas.


package My::Base;
use Moose;
sub override_me { shift; return @_; }

Eu prefiro usar

sub do_something_fantastical {
    my ( $foo, $bar, $baz, $qux, $quux, $corge ) = @_;
}

Porque é mais legível. Quando esse código não é chamado com muita frequência, vale a pena. Em casos muito raros, você deseja fazer a função chamada frequentemente e não use @_ diretamente. É eficaz apenas para funções muito curtas e você deve ter certeza de que essa função não evoluirá no futuro (escreva uma função depois). Este caso, comparei em 5.8.8 que, para o parâmetro único, é alterado mais rápido que $ _ [0], mas para dois parâmetros usando $ _ [0] e $ _ [1] é mais rápido que o turno, Shift.

sub fast1 { shift->call(@_) }

sub fast2 { $_[0]->call("a", $_[1]) }

Mas de volta à sua pergunta. Eu também prefiro a atribuição @_ em uma linha a mais de turnos para muitos parâmetros dessa maneira

sub do_something_fantastical2 {
    my ( $foo, $bar, $baz, @rest ) = @_;
    ...
}

Quando o suspeito @rest não será muito grande. Em outro caso

sub raise {
    my $inc = shift;
    map {$_ + $inc} @_;
}

sub moreSpecial {
    my ($inc, $power) = (shift(), shift());
    map {($_ + $inc) ** $power} @_;
}

sub quadratic {
    my ($a, $b, $c) = splice @_, 0, 3;
    map {$a*$_*$_ + $b*$_ + $c} @_;
}

Nos casos raramente, preciso de otimização de chamadas de cauda (à mão, é claro), então devo trabalhar diretamente com @_, do que para uma função curta vale a pena.

sub _switch    #(type, treeNode, transform[, params, ...])
{
    my $type = shift;
    my ( $treeNode, $transform ) = @_;
    unless ( defined $type ) {
        require Data::Dumper;
        die "Broken node: " . Data::Dumper->Dump( $treeNode, ['treeNode'] );
    }
    goto &{ $transform->{$type} }   if exists $transform->{$type};
    goto &{ $transform->{unknown} } if exists $transform->{unknown};
    die "Unknown type $type";
}

sub switchExTree    #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, $treeNode->{type};    # set type
    goto &_switch;                    # tail call
}

sub switchCompact                     #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, (%$treeNode)[0];      # set type given as first key
    goto &_switch;                    # tail call
}

sub ExTreeToCompact {
    my $tree = shift;
    return switchExTree( $tree, \%transformExTree2Compact );
}

sub CompactToExTree {
    my $tree = shift;
    return switchCompact( $tree, \%transformCompact2ExTree );
}

Onde %o transformExtree2Compact e %TransformCompact2Extree são hashes com o tipo de chave e codificam o valor que podem fazer o switchExtree de chamada ou o switchcompact. Mas Essa abordagem raramente é realmente precisa e deve manter menos valios a pena a faculdade dos dedos.

Em conclusão, a legibilidade e a manutenção são, especialmente, em Perl e a atribuição de @_ em uma linha é melhor. Se você deseja definir os padrões, poderá fazê -lo logo após.

A melhor maneira, IMHO, é uma ligeira mistura dos dois, como na nova função de um módulo:

our $Class;    
sub new {
    my $Class = shift;
    my %opts = @_;
    my $self = \%opts;
    # validate %opts for correctness
    ...
    bless $self, $Class;
}

Então, todos os argumentos de chamada para o construtor são passados ​​como um hash, o que torna o código muito mais legível do que apenas uma lista de parâmetros.

Além disso, como Brian disse, o @_ variável não é modificada, o que pode ser útil em casos incomuns.

Eu suspeito que se você estiver fazendo o equivalente (áspero) de:

push @bar, shift @_ for (1 :: $big_number);

Então você está fazendo algo errado. Eu sempre uso o my ($foo, $bar) = @_; FORM porque eu me atirei no pé usando o último algumas vezes ...

eu prefiro

sub factorial{
  my($n) = @_;

  ...

}

Pela simples razão pela qual Komodo poderá me dizer quais são os argumentos, quando eu for usá -lo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top