2009-08-25 5 views
26

Ich verwende häufig shift Funktionsparameter zu entpacken:Ist 'shift' schlecht für die Verarbeitung von Perl-Unterprogrammparametern?

Predigt
sub my_sub { 
    my $self = shift; 
    my $params = shift; 
    .... 
} 

jedoch viele auf meine Kollegen, dass shift wirklich böse ist. Könnten Sie erklären, warum ich es bevorzugen sollte

sub my_sub { 
    my ($self, $params) = @_; 
    .... 
} 

bis shift?

+12

Ich glaube, Sie sollten Ihre Kollegen fragen, warum sie denken, dass Verschiebung böse ist und gemeinsam mit uns hier ihre Antworten in SO – ccheneson

+7

@Bombe, ich sehe, dass Sie PHP ziemlich viel verwenden, aber Sie haben das Bedürfnis, beleidigend zu sein über Perl. Ich hoffe, Ihnen ist bewusst, dass die meisten der üblichen Beschwerden über Perl auch für PHP gelten. Jede Sprache hat ihre hässlichen Teile, aber die meisten haben etwas Gutes zu bieten, das es wert ist, damit umzugehen. Um es klar zu sagen, PHP ist, wie Perl, ein großartiges Werkzeug, mit dem wir freien Zugang haben - das gleiche gilt für Java, Python, Ruby und so weiter. Bitte beleidigen Sie nicht die Wahl der Werkzeuge. Ein Smiley ändert nicht die Art Ihres Kommentars. Eine Beleidigung mit einem Lächeln ist immer noch eine Beleidigung. – daotoad

+1

Für was es wert ist, rassie, jemand, der dogmatisch eine Eigenschaft von Perl gegenüber einem anderen befürwortet, fehlt der Punkt von Perl. –

Antwort

49

Die Verwendung von shift Argumente zu entpacken ist nicht böse. Es ist eine gängige Konvention und möglicherweise der schnellste Weg, um Argumente zu verarbeiten (abhängig davon, wie viele es sind und wie sie übergeben werden). Hier ist ein Beispiel für ein ziemlich häufiges Szenario, wo das der Fall ist: ein einfacher Accessor.

use Benchmark qw(cmpthese); 

sub Foo::x_shift { shift->{'a'} } 
sub Foo::x_ref { $_[0]->{'a'} } 
sub Foo::x_copy { my $s = $_[0]; $s->{'a'} } 

our $o = bless {a => 123}, 'Foo'; 

cmpthese(-2, { x_shift => sub { $o->x_shift }, 
       x_ref => sub { $o->x_ref }, 
       x_copy => sub { $o->x_copy }, }); 

Die Ergebnisse auf Perl 5.8.8 auf meinem Rechner:

  Rate x_copy x_ref x_shift 
x_copy 772761/s  -- -12% -19% 
x_ref 877709/s  14%  --  -8% 
x_shift 949792/s  23%  8%  -- 

Nicht dramatisch, aber es ist so. Testen Sie Ihr Szenario immer auf Ihrer Perl-Version auf Ihrer Zielhardware, um es herauszufinden.

shift ist auch nützlich in Fällen, in denen Sie die Aufrufer verschieben weg wollen und dann eine SUPER:: Methode aufrufen, vorbei an der verbleibenden @_ wie sie ist.

sub my_method 
{ 
    my $self = shift; 
    ... 
    return $self->SUPER::my_method(@_); 
} 

Wenn ich an der Spitze einer Funktion eine sehr lange Reihe von my $foo = shift; Operationen hatte jedoch könnte ich stattdessen mit einer Massenkopie von @_ betrachten. Aber im Allgemeinen, wenn Sie eine Funktion oder eine Methode haben, die mehr als eine Handvoll Argumente benötigt, ist die Verwendung von benannten Parametern (d. H. Alle @_ in einem %args Hash zu fangen oder ein einzelnes Hash-Referenzargument zu erwarten) ein viel besserer Ansatz.

+6

Sie sollten zeigen, wenn es tatsächlich schneller ist, keine Schicht zu verwenden. Ich denke, mit 2 oder 3 Argumenten ist es schneller, @_ zu kopieren. Als Faustregel verwende ich shift, wenn es 1 arg gibt, und kopiere @_, wenn es mehr als 1 gibt. – mpeters

0

Es ist nicht von Natur aus böse, aber es die Argumente eines Unterprogramms nacheinander zu verwenden, ist vergleichsweise langsam und erfordert eine größere Anzahl von Codezeilen.

+2

-1 für falsche Informationen. Wie @John Siracusa sagt, ist es ein gängiges Perl-Idiom, die Verschiebung zu verwenden, um Argumente zu verarbeiten. –

+3

Ich sehe nicht, wo er bestritten hat, dass es kein gewöhnliches Perl-Idiom war. Er hat gesagt, dass es langsamer ist (was richtig ist, weil das Argumentarray manipuliert werden muss, anstatt nur darauf zuzugreifen). – JoshJordan

+5

Tony hat recht, irgendwie. Basierend auf meinem Benchmarking ist die Verschiebung für 2 Parameter oder weniger schneller. Listenzuordnung ist schneller für 3 oder mehr. Da alles, was 3 oder 4 oder 10 Params benötigt, wahrscheinlich keine Positionsparameter auf diese Weise machen sollte, ist die Verwendung des Shift-Stils sinnvoll. –

20

Es ist nicht böse, es ist eine Art Geschmack. Sie werden oft die Stile zusammen gebraucht:

sub new { 
    my $class = shift; 
    my %self = @_; 
    return bless \%self, $class; 
} 

Ich neige dazu, shift zu verwenden, wenn es ein Argument ist, oder wenn ich will die ersten Argumente anders als der Rest behandeln.

8

Die Zuweisung von @_ zu einer Liste kann einige zusätzliche Funktionen enthalten.

Es macht es etwas leichter, zu einem späteren Zeitpunkt zusätzliche benannte Parameter hinzuzufügen, wenn Sie Ihren Code ändern Manche Leute betrachten dies als eine Funktion, ähnlich wie das Beenden einer Liste oder ein Hash mit einem abschließenden ',' es etwas leichter macht um Mitglieder in Zukunft anzuhängen.

Wenn Sie es gewohnt sind, dieses Idiom zu verwenden, scheint die Verschiebung der Argumente schädlich zu sein. Wenn Sie den Code bearbeiten, um ein zusätzliches Argument hinzuzufügen, könnte dies zu einem kleinen Fehler führen Passt auf.

z.B.

sub do_something { 
my ($foo) = @_; 
} 

später

sub do_something { 
    my ($foo,$bar) = @_; # still works 
} 

sub do_another_thing { 
    my $foo = shift; 
} 

Wenn ein anderer Kollege jedoch bearbeitet, der die erste Form verwendet dogmatisch (vielleicht denken, sie Verschiebung böse ist) bearbeitet Dateien und abwesenden -mindedly dies zu lesen

sub do_another_thing { 
    my ($foo, $bar) = shift; # still 'works', but $bar not defined 
} 

und sie haben möglicherweise einen subtilen Fehler eingeführt.

Die Zuweisung zu @_ kann kompakter und effizienter sein, wenn Sie eine kleine Anzahl von Parametern gleichzeitig zuweisen müssen. Sie können auch Standardargumente angeben, wenn Sie den Hash-Stil der benannten Funktionsparameter

verwenden, z.

my (%arguments) = (user=>'defaultuser',password=>'password',@_); 

Ich würde es immer noch als eine Frage von Stil/Geschmack betrachten. Ich denke, das Wichtigste ist es, den einen oder anderen Stil konsequent anzuwenden, indem man dem Prinzip der geringsten Überraschung folgt.

+2

Ich glaube nicht, dass 'my ($ foo, $ bar) = shift;' ein * subtiler * Bug ist . Es ist ein Stein am Kopf, nicht? – Telemachus

+2

Ich habe es zu oft passieren gesehen. Dummer Fehler natürlich, aber es ist überraschend einfach, "blind zu werden", wenn man viel liest. – cms

+0

In der Regel wird beim Entpacken per Shift ein zusätzlicher Parameter hinzugefügt, indem eine weitere Zeile hinzugefügt wird. 'my $ foo = Verschiebung; meine $ bar = shift; 'Ich habe nie gesehen, was du beschreibst – daotoad

1

Ich glaube nicht shift ist böse. Die Verwendung von shift zeigt Ihre Bereitschaft, Variablen tatsächlich zu benennen - anstatt $_[0] zu verwenden.

Persönlich verwende ich shift, wenn es nur einen Parameter zu einer Funktion gibt. Wenn ich mehr als einen Parameter habe, verwende ich den Listenkontext.

my $result = decode($theString); 

sub decode { 
    my $string = shift; 

    ... 
} 

my $otherResult = encode($otherString, $format); 

sub encode { 
    my ($string,$format) = @_; 
    ... 
} 
11

Dies ist, wie andere gesagt haben, eine Frage des Geschmacks.

ziehe ich meine Parameter in Lexikale zu verschieben, weil es mir einen Standard-Platz gibt eine Gruppe von Variablen zu deklarieren, die in dem Unterprogramm verwendet werden. Die zusätzliche Ausführlichkeit gibt mir einen schönen Ort, um Kommentare zu hängen. Es erleichtert auch die Bereitstellung von Standardwerten auf eine saubere Art und Weise.

sub foo { 
    my $foo = shift;  # a thing of some sort. 
    my $bar = shift;  # a horse of a different color. 
    my $baz = shift || 23; # a pale horse. 

    # blah 
} 

Wenn Sie wirklich besorgt über die Geschwindigkeit Ihrer Routinen aufzurufen, nicht auspacken Ihre Argumente überhaupt - mit ihnen direkt @_ zugreifen. Seien Sie vorsichtig, das sind Verweise auf die Daten des Anrufers, mit denen Sie arbeiten. Dies ist eine allgemeine Redewendung in POE. POE bietet eine Reihe von Konstanten, die Sie erhalten verwenden, um Namen Positionsparameter durch:

sub some_poe_state_handler { 
    $_[HEAP]{some_data} = 'chicken'; 
    $_[KERNEL]->yield('next_state'); 
} 

Nun ist die große dumme Fehler, den Sie erhalten, wenn Sie gewohnheitsmäßig params mit Verschiebung entpacken ist diese:

sub foo { 
    my $foo = shift; 
    my $bar = shift; 
    my @baz = shift; 

    # I should really stop coding and go to bed. I am making dumb errors. 

} 

I denke, dass konsistenter Code-Stil wichtiger ist als jeder bestimmte Stil. Wenn alle meine Mitarbeiter den Listenzuweisungsstil verwenden würden, würde ich ihn auch verwenden.

Wenn meine Mitarbeiter sagen, dass es ein großes Problem ist Verschiebung mit auszupacken, würde ich für eine Demonstration fragen, warum es schlecht ist. Wenn der Fall solide ist, würde ich etwas lernen. Wenn der Fall falsch ist, könnte ich ihn dann widerlegen und helfen, die Verbreitung von Anti-Wissen zu stoppen. Dann würde ich vorschlagen, dass wir eine Standardmethode bestimmen und sie für zukünftigen Code befolgen.Ich könnte sogar versuchen, eine Perl :: Critic-Richtlinie einzurichten, um nach dem festgelegten Standard zu suchen.

+1

+1, aber beachten Sie, dass, wenn die Argumente Kommentare benötigen (und es gibt mehr als zwei von ihnen), besser, benannte Argumente zu verwenden und einen Standard-Hash bereitzustellen. –

+0

Normalerweise setze ich mein Limit auf 3, aber absolut. – daotoad

1

Perl :: Kritiker ist dein Freund hier. Es folgt den "Standards", die Damian Conways Buch Perl Best Practices aufstellt. Wenn Sie es mit --verbose 11 ausführen, erklären Sie, warum die Dinge schlecht sind. Das erste Auspacken von @_ in deinen Subs ist ein Schweregrad von 4 (von 5). Beispiel:

echo 'package foo; use warnings; use strict; sub quux { my foo= shift; my (bar,baz) = @_;};1;' | perlcritic -4 --verbose 11 

Always unpack @_ first at line 1, near 'sub quux { my foo= shift; my (bar,baz) = @_;}'. 
    Subroutines::RequireArgUnpacking (Severity: 4) 
    Subroutines that use `@_' directly instead of unpacking the arguments to 
    local variables first have two major problems. First, they are very hard 
    to read. If you're going to refer to your variables by number instead of 
    by name, you may as well be writing assembler code! Second, `@_' 
    contains aliases to the original variables! If you modify the contents 
    of a `@_' entry, then you are modifying the variable outside of your 
    subroutine. For example: 

     sub print_local_var_plus_one { 
      my ($var) = @_; 
      print ++$var; 
     } 
     sub print_var_plus_one { 
      print ++$_[0]; 
     } 

     my $x = 2; 
     print_local_var_plus_one($x); # prints "3", $x is still 2 
     print_var_plus_one($x);  # prints "3", $x is now 3 ! 
     print $x;      # prints "3" 

    This is spooky action-at-a-distance and is very hard to debug if it's 
    not intentional and well-documented (like `chop' or `chomp'). 

    An exception is made for the usual delegation idiom 
    `$object->SUPER::something(@_)'. Only `SUPER::' and `NEXT::' are 
    recognized (though this is configurable) and the argument list for the 
    delegate must consist only of `(@_)'. 
+3

Dies scheint irrtümlicherweise einen Fall zuzuordnen, in dem @_ direkt verwendet wird, anstatt lokale Kopien zu verwenden. Das ist in der Tat fast immer das Falsche. Weder das Verschieben noch das Zuweisen von @_ zu einer Liste leiden jedoch unter den in der Ausgabe beschriebenen Problemen. Allerdings könnte Cargo-Kult um den Output dieses Utility erklären, warum Leute denken, dass Betreiber wie Verschiebung böse sind, nehme ich an. – cms

+1

Setzen Sie Perl Best Practices nicht zu sehr zu. Es gibt zu viele Situationen, um eine einzelne Regel auf alle anzuwenden. –

1

Es gibt eine Optimierung für die Listenzuordnung.

Die einzige Referenz, die ich finden konnte, ist this one.

5.10.0 versehentlich eine Optimierung, die verursachte einen messbaren Leistungsabfall in Liste Zuordnung, wie beispielsweise wird oft verwendet, um assign Funktionsparameter von @_ deaktiviert. Die Optimierung wurde wieder eingefügt, und die Performance-Regression behoben.

Dies ist ein Beispiel für die betroffene Leistungsregression.

sub example{ 
    my($arg1,$arg2,$arg3) = @_; 
} 
+0

Wenn jemand eine bessere Referenz hat, zögern Sie nicht, einen Kommentar zu hinterlassen. –

Verwandte Themen