Warum sind die Funktionsprototypen von Perl 5 schlecht?

116

In einer anderen Frage zum Stapelüberlauf behauptete Leon Timmermans :

Ich würde Ihnen raten, keine Prototypen zu verwenden. Sie haben ihre Verwendung, aber nicht für die meisten Fälle und definitiv nicht in dieser.

Warum könnte dies wahr sein (oder anders)? Ich liefere fast immer Prototypen für meine Perl-Funktionen, und ich habe noch nie jemanden gesehen, der etwas Schlechtes über ihre Verwendung gesagt hat.

Alnitak
quelle
Ich bin auch neugierig. Ich benutze sie nur dann nicht, wenn ich mit einer variablen Anzahl von Argumenten aufrufe.
Paul Tomblin
7
Darf ich Ihnen empfehlen, den Artikel „Perl-Prototypen als schädlich“ zu lesen ?
Tchrist

Antworten:

121

Prototypen sind nicht schlecht, wenn sie richtig verwendet werden. Die Schwierigkeit besteht darin, dass Perls Prototypen nicht so funktionieren, wie es die Leute oft erwarten. Personen mit einem Hintergrund in anderen Programmiersprachen erwarten in der Regel, dass Prototypen einen Mechanismus zur Überprüfung der Richtigkeit von Funktionsaufrufen bereitstellen: Das heißt, sie haben die richtige Anzahl und Art von Argumenten. Perls Prototypen sind für diese Aufgabe nicht gut geeignet. Es ist der Missbrauch , der schlimm ist. Perls Prototypen haben einen einzigartigen und ganz anderen Zweck:

Mit Prototypen können Sie Funktionen definieren, die sich wie integrierte Funktionen verhalten.

  • Klammern sind optional.
  • Den Argumenten wird ein Kontext auferlegt.

Sie können beispielsweise eine Funktion wie die folgende definieren:

sub mypush(\@@) { ... }

und nenne es als

mypush @array, 1, 2, 3;

ohne dass Sie das schreiben müssen \, um einen Verweis auf das Array zu erhalten.

Kurz gesagt, mit Prototypen können Sie Ihren eigenen syntaktischen Zucker herstellen. Das Moose-Framework verwendet sie beispielsweise, um eine typischere OO-Syntax zu emulieren.

Dies ist sehr nützlich, aber die Prototypen sind sehr begrenzt:

  • Sie müssen zur Kompilierungszeit sichtbar sein.
  • Sie können umgangen werden.
  • Die Weitergabe des Kontexts an Argumente kann zu unerwartetem Verhalten führen.
  • Sie können es schwierig machen, Funktionen mit etwas anderem als der streng vorgeschriebenen Form aufzurufen.

Unter Prototypen in Perlsub finden Sie alle wichtigen Details.

Michael Carman
quelle
2
Ich habe diese Antwort akzeptiert, weil ich der Meinung bin, dass sie die Frage am besten beantwortet - Prototypen sind nicht an sich schlecht, sondern nur so, wie Sie sie verwenden.
Alnitak
2
Elch-Prototypen hingegen sind / awesome / p3rl.org/MooseX::Declare p3rl.org/MooseX::Method::Signatures
Kent Fredric
Also sind sie eine Fehlbezeichnung?
Peter Mortensen
69

Das Problem ist, dass Perls Funktionsprototypen nicht das tun, was die Leute denken. Sie sollen es Ihnen ermöglichen, Funktionen zu schreiben, die wie die integrierten Funktionen von Perl analysiert werden.

Erstens ignorieren Methodenaufrufe Prototypen vollständig. Wenn Sie OO-Programmierung durchführen, spielt es keine Rolle, welchen Prototyp Ihre Methoden haben. (Sie sollten also keinen Prototyp haben.)

Zweitens werden Prototypen nicht strikt durchgesetzt. Wenn Sie eine Unterroutine mit aufrufen &function(...), wird der Prototyp ignoriert. Sie bieten also keine wirkliche Sicherheit.

Drittens sind sie gruselig auf Distanz. (Insbesondere der $Prototyp, bei dem der entsprechende Parameter im skalaren Kontext anstelle des Standardlistenkontexts ausgewertet wird.)

Insbesondere erschweren sie die Übergabe von Parametern aus Arrays. Beispielsweise:

my @array = qw(a b c);

foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);

sub foo ($;$$) { print "@_\n" }

foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);

Drucke:

a b c
a b
a b c
3
b
a b c

zusammen mit 3 Warnungen über main::foo() called too early to check prototype(wenn Warnungen aktiviert sind). Das Problem ist, dass ein im skalaren Kontext ausgewertetes Array (oder Array-Slice) die Länge des Arrays zurückgibt.

Wenn Sie eine Funktion schreiben müssen, die sich wie eine integrierte Funktion verhält, verwenden Sie einen Prototyp. Verwenden Sie andernfalls keine Prototypen.

Hinweis: Perl 6 wird komplett überarbeitet und sehr nützliche Prototypen haben. Diese Antwort gilt nur für Perl 5.

cjm
quelle
Aber sie bieten immer noch eine nützliche Überprüfung, ob Ihr Anrufer und das Sub die gleiche Anzahl von Argumenten verwenden. Was ist daran falsch?
Paul Tomblin
2
Nein; Der allgemeine Konsens ist, dass Perl-Funktionsprototypen im Wesentlichen keinen Nutzen bieten. Sie können sich genauso gut nicht darum kümmern, zumindest in Perl 5. Perl 6 könnte eine andere (bessere) Geschichte sein.
Jonathan Leffler
5
Es gibt bessere Möglichkeiten, Argumente zu validieren, z. B. das Params :: Validate-Modul: search.cpan.org/~drolsky/Params-Validate-0.91/lib/Params/…
friedo
10
Korrektur: Das Array-Slicing gibt eine Liste zurück , sodass ein Array-Slice in einem skalaren Kontext das letzte Element der Liste zurückgibt. Ihr vorletzter Aufruf von foo()Ausdrucken 2, da dies das letzte Element in Ihrem Slice mit zwei Elementen ist. Wechseln Sie zu my @array = qw(foo bar baz)und Sie werden den Unterschied sehen. (Abgesehen davon initialisiere ich Arrays / Listen aus diesem Grund nicht mit 0- oder 1-basierten numerischen Sequenzen in wegwerfbarem, demonstrativem Code. Die Verwirrung zwischen Indizes, Zählungen und Elementen in Kontexten hat mich mehr als einmal gebissen.
Dumm
2
@pilcrow: Ich habe die Antwort bearbeitet a b c, um Ihren Standpunkt klarer zu machen.
Flimm
30

Ich stimme den beiden oben genannten Postern zu. Im Allgemeinen $sollte die Verwendung vermieden werden. Prototypen sind nur dann sinnvoll , wenn der Block Argumente ( &), Klümpchen ( *) oder Referenzprototypen ( \@, \$, \%, \*)

Leon Timmermans
quelle
Im Allgemeinen vielleicht, aber ich möchte zwei Ausnahmen erwähnen: Erstens erstellt der ($)Prototyp einen benannten unären Operator, der nützlich sein kann (sicherlich findet Perl sie nützlich; ich habe gelegentlich auch). Zweitens sollten Sie sich beim Überschreiben von integrierten Funktionen (ob durch Import oder Verwendung von CORE :: GLOBAL: :) im Allgemeinen an den Prototyp halten, den die integrierte Version enthält, auch wenn dies eine enthält $, oder Sie könnten den Programmierer (sich selbst) überraschen. gerade) mit Listenkontext, in dem der integrierte Kontext sonst einen skalaren Kontext bereitstellen würde.
Die Sidhekin
4

Einige Leute, die sich einen Perl-Subroutinen-Prototyp ansehen, denken, dass dies etwas bedeutet, was es nicht bedeutet:

sub some_sub ($$) { ... }

Für Perl bedeutet dies, dass der Parser zwei Argumente erwartet. Auf diese Weise können Sie mit Perl Unterprogramme erstellen, die sich wie integrierte Funktionen verhalten und alle wissen, was Sie vom nachfolgenden Code erwarten können. Sie können über Prototypen in Perlsub lesen

Ohne die Dokumentation zu lesen, vermuten die Leute, dass sich die Prototypen auf die Überprüfung von Laufzeitargumenten oder etwas Ähnliches beziehen, das sie in anderen Sprachen gesehen haben. Wie bei den meisten Dingen, die Leute über Perl erraten, erweisen sie sich als falsch.

Ab Perl v5.20 verfügt Perl jedoch über eine experimentelle Funktion, die experimentell ist, wenn ich dies schreibe, und die eher das bietet, was Benutzer erwarten und was. Perls Subroutinensignaturen zählen Laufzeitargumente, Variablenzuweisungen und Standardeinstellungen:

use v5.20;
use feature qw(signatures);
no warnings qw(experimental::signatures);

animals( 'Buster', 'Nikki', 'Godzilla' );

sub animals ($cat, $dog, $lizard = 'Default reptile') { 
    say "The cat is $cat";
    say "The dog is $dog";
    say "The lizard is $lizard";
    }

Dies ist die Funktion, die Sie wahrscheinlich wünschen, wenn Sie Prototypen in Betracht ziehen.

brian d foy
quelle