Pourquoi les prototypes de fonctions de Perl 5 sont-ils mauvais?

116

Dans une autre question de Stack Overflow, Leon Timmermans a affirmé:

Je vous conseillerais de ne pas utiliser de prototypes. Ils ont leurs utilisations, mais pas dans la plupart des cas et certainement pas dans celui-ci.

Pourquoi cela pourrait-il être vrai (ou non)? Je fournis presque toujours des prototypes pour mes fonctions Perl, et je n'ai jamais vu personne d'autre dire quelque chose de mal sur leur utilisation.

Alnitak
la source
Je suis curieux aussi. Le seul moment où je ne les utilise pas, c'est lorsque j'appelle avec un nombre variable d'arguments.
Paul Tomblin
7
Puis-je vous recommander de lire l'article «Les prototypes Perl considérés comme nuisibles» ?
tchrist

Réponses:

121

Les prototypes ne sont pas mauvais s'ils sont utilisés correctement. Le problème est que les prototypes de Perl ne fonctionnent pas comme les gens l'attendent souvent. Les personnes ayant une expérience dans d'autres langages de programmation ont tendance à s'attendre à ce que les prototypes fournissent un mécanisme pour vérifier que les appels de fonction sont corrects, c'est-à-dire qu'ils ont le bon nombre et le bon type d'arguments. Les prototypes de Perl ne sont pas bien adaptés à cette tâche. C'est le mauvais usage qui est mauvais. Les prototypes de Perl ont un but singulier et très différent:

Les prototypes vous permettent de définir des fonctions qui se comportent comme des fonctions intégrées.

  • Les parenthèses sont facultatives.
  • Le contexte est imposé aux arguments.

Par exemple, vous pouvez définir une fonction comme celle-ci:

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

et appelez-le comme

mypush @array, 1, 2, 3;

sans avoir besoin d'écrire le \pour prendre une référence au tableau.

En un mot, les prototypes vous permettent de créer votre propre sucre syntaxique. Par exemple, le framework Moose les utilise pour émuler une syntaxe OO plus typique.

C'est très utile mais les prototypes sont très limités:

  • Ils doivent être visibles au moment de la compilation.
  • Ils peuvent être contournés.
  • La propagation du contexte aux arguments peut provoquer un comportement inattendu.
  • Ils peuvent rendre difficile l'appel de fonctions en utilisant autre chose que la forme strictement prescrite.

Voir Prototypes dans perlsub pour tous les détails sanglants.

Michael Carman
la source
2
J'ai accepté cette réponse parce que je pense qu'elle répond le mieux à la question - les prototypes ne sont pas intrinsèquement mauvais, c'est juste la façon dont vous les utilisez.
Alnitak
2
Les prototypes de Moose, quant à eux, sont / awesome / p3rl.org/MooseX::Declare p3rl.org/MooseX::Method::Signatures
Kent Fredric
Alors, c'est un abus de langage?
Peter Mortensen
69

Le problème est que les prototypes de fonction de Perl ne font pas ce que les gens pensent faire. Leur but est de vous permettre d'écrire des fonctions qui seront analysées comme les fonctions intégrées de Perl.

Tout d'abord, les appels de méthode ignorent complètement les prototypes. Si vous faites de la programmation OO, peu importe le prototype de vos méthodes. (Ils ne devraient donc pas avoir de prototype.)

Deuxièmement, les prototypes ne sont pas strictement appliqués. Si vous appelez un sous-programme avec &function(...), le prototype est ignoré. Donc, ils n'offrent pas vraiment de sécurité de type.

Troisièmement, ils sont une action effrayante à distance. (Surtout le $prototype, qui provoque l'évaluation du paramètre correspondant dans un contexte scalaire, au lieu du contexte de liste par défaut.)

En particulier, ils rendent difficile la transmission de paramètres à partir de tableaux. Par exemple:

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

imprime:

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

avec 3 avertissements sur main::foo() called too early to check prototype(si les avertissements sont activés). Le problème est qu'un tableau (ou une tranche de tableau) évalué dans un contexte scalaire renvoie la longueur du tableau.

Si vous avez besoin d'écrire une fonction qui agit comme une fonction intégrée, utilisez un prototype. Sinon, n'utilisez pas de prototypes.

Remarque: Perl 6 aura des prototypes complètement repensés et très utiles. Cette réponse s'applique uniquement à Perl 5.

cjm
la source
Mais ils fournissent toujours une vérification utile que votre appelant et le sous-marin utilisent le même nombre d'arguments, alors qu'est-ce qui ne va pas?
Paul Tomblin
2
Non; le consensus général est que les prototypes de fonction Perl ne fournissent pratiquement aucun avantage. Vous pouvez aussi bien ne pas vous en préoccuper, du moins en Perl 5. Perl 6 pourrait être une autre (meilleure) histoire.
Jonathan Leffler
5
Il existe de meilleures façons de valider les arguments, comme le module Params :: Validate: search.cpan.org/~drolsky/Params-Validate-0.91/lib/Params/…
friedo
10
Correction: le découpage de tableau renvoie une liste , donc une tranche de tableau dans un contexte scalaire renvoie l'élément final de la liste. Votre avant-dernier appel des foo()impressions 2 car c'est le dernier élément de votre tranche à deux éléments. Changez pour my @array = qw(foo bar baz)et vous verrez la différence. (En passant, c'est pourquoi je n'initialise pas les tableaux / listes à des séquences numériques basées sur 0 ou 1 dans un code démonstratif jetable. La confusion entre les indices, les décomptes et les éléments dans les contextes m'a mordu plus d'une fois. Silly mais vrai.)
Pilcrow
2
@pilcrow: j'ai modifié la réponse à utiliser a b cpour clarifier votre point de vue.
Flimm
30

Je suis d'accord avec les deux affiches ci-dessus. En général, l'utilisation $doit être évitée. Prototypes ne sont utiles que lors de l' utilisation de blocs (arguments &), (globules *) ou prototypes de référence ( \@, \$, \%, \*)

Léon Timmermans
la source
En général, peut-être, mais je voudrais mentionner deux exceptions: Premièrement, le ($)prototype crée un opérateur unaire nommé, qui peut être utile (certainement Perl les trouve utiles; moi aussi, à l'occasion). Deuxièmement, lorsque vous remplacez les composants intégrés (que ce soit via l'importation ou en utilisant CORE :: GLOBAL: :), vous devez en général vous en tenir à n'importe quel prototype intégré, même si cela inclut un $, ou vous pourriez surprendre le programmeur (vous-même, even) avec un contexte de liste où le intégré fournirait autrement un contexte scalaire.
The Sidhekin
4

Certaines personnes, en regardant un prototype de sous-programme Perl, pensent que cela signifie quelque chose que ce n'est pas:

sub some_sub ($$) { ... }

Pour Perl, cela signifie que l'analyseur attend deux arguments. C'est la façon dont Perl vous permet de créer des sous-programmes qui se comportent comme des composants intégrés, qui savent tous à quoi s'attendre du code suivant. Vous pouvez en savoir plus sur les prototypes dans perlsub

Sans lire la documentation, les gens supposent que les prototypes font référence à la vérification des arguments d'exécution ou à quelque chose de similaire qu'ils ont vu dans d'autres langues. Comme pour la plupart des choses que les gens pensent à propos de Perl, elles se révèlent fausses.

Cependant, à partir de Perl v5.20, Perl a une fonctionnalité, expérimentale au moment où j'écris ceci, qui donne quelque chose de plus comme ce que les utilisateurs attendent et quoi. Les signatures de sous - programmes de Perl exécutent le comptage des arguments, l'attribution des variables et les paramètres par défaut:

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

C'est la fonctionnalité que vous souhaitez probablement si vous envisagez des prototypes.

brian d foy
la source