Perché i prototipi di funzioni di Perl 5 sono cattivi?


116

In un'altra domanda su Stack Overflow Leon Timmermans ha affermato:

Ti consiglio di non utilizzare prototipi. Hanno i loro usi, ma non per la maggior parte dei casi e sicuramente non in questo.

Perché questo potrebbe essere vero (o altrimenti)? Fornisco quasi sempre prototipi per le mie funzioni Perl, e non ho mai visto nessun altro dire qualcosa di negativo sul loro utilizzo.


Anch'io sono curioso. L'unica volta che non li uso è quando chiamo con un numero variabile di argomenti.
Paul Tomblin,

7
Posso per favore consigliarvi di leggere l'articolo "Prototipi Perl considerati dannosi" ?
tchrist

Risposte:


121

I prototipi non sono male se usati correttamente. La difficoltà è che i prototipi di Perl non funzionano nel modo in cui le persone spesso si aspettano che funzionino. Le persone con un background in altri linguaggi di programmazione tendono ad aspettarsi che i prototipi forniscano un meccanismo per verificare che le chiamate di funzione siano corrette: cioè, che abbiano il giusto numero e tipo di argomenti. I prototipi di Perl non sono adatti per questo compito. È l' abuso che fa male. I prototipi di Perl hanno uno scopo singolare e molto diverso:

I prototipi consentono di definire funzioni che si comportano come funzioni incorporate.

  • Le parentesi sono facoltative.
  • Il contesto è imposto agli argomenti.

Ad esempio, potresti definire una funzione come questa:

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

e chiamalo come

mypush @array, 1, 2, 3;

senza bisogno di scrivere il \per prendere un riferimento all'array.

In poche parole, i prototipi ti consentono di creare il tuo zucchero sintattico. Ad esempio, il framework Moose li utilizza per emulare una sintassi OO più tipica.

Questo è molto utile ma i prototipi sono molto limitati:

  • Devono essere visibili in fase di compilazione.
  • Possono essere aggirati.
  • La propagazione del contesto agli argomenti può causare un comportamento imprevisto.
  • Possono rendere difficile chiamare le funzioni utilizzando qualcosa di diverso dalla forma strettamente prescritta.

Vedi Prototipi in perlsub per tutti i dettagli cruenti.


2
Ho accettato questa risposta perché penso che risponda meglio alla domanda: i prototipi non sono intrinsecamente cattivi, è solo come li usi.
Alnitak

2
I prototipi di alci, d'altra parte, sono / awesome / p3rl.org/MooseX::Declare p3rl.org/MooseX::Method::Signatures
Kent Fredric


Quindi sono un termine improprio, quindi?
Peter Mortensen

69

Il problema è che i prototipi di funzioni di Perl non fanno quello che la gente pensa di fare. Il loro scopo è quello di consentire di scrivere funzioni che verranno analizzate come le funzioni integrate di Perl.

Prima di tutto, le chiamate ai metodi ignorano completamente i prototipi. Se stai facendo programmazione OO, non importa quale prototipo hanno i tuoi metodi. (Quindi non dovrebbero avere alcun prototipo.)

Secondo, i prototipi non vengono applicati rigorosamente. Se chiami una subroutine con &function(...), il prototipo viene ignorato. Quindi in realtà non forniscono alcuna protezione dai tipi.

Terzo, sono azioni spettrali a distanza. (Soprattutto il $prototipo, che fa sì che il parametro corrispondente venga valutato nel contesto scalare, invece del contesto dell'elenco predefinito.)

In particolare, rendono difficile il passaggio di parametri dagli array. Per esempio:

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

stampe:

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

insieme a 3 avvisi su main::foo() called too early to check prototype(se gli avvisi sono abilitati). Il problema è che un array (o slice di array) valutato in contesto scalare restituisce la lunghezza dell'array.

Se hai bisogno di scrivere una funzione che agisca come un built-in, usa un prototipo. Altrimenti, non utilizzare prototipi.

Nota: Perl 6 avrà prototipi completamente rinnovati e molto utili. Questa risposta si applica solo a Perl 5.


Ma forniscono comunque un utile controllo che il chiamante e il sub utilizzino lo stesso numero di argomenti, quindi cosa c'è di sbagliato in questo?
Paul Tomblin,

2
No; il consenso generale è che i prototipi di funzioni Perl non forniscono essenzialmente alcun vantaggio. Potresti anche non preoccuparti di loro, almeno in Perl 5. Perl 6 potrebbe essere una storia diversa (migliore).
Jonathan Leffler

5
Esistono modi migliori per convalidare gli argomenti, come il modulo Params :: Validate: search.cpan.org/~drolsky/Params-Validate-0.91/lib/Params/…
friedo

10
Correzione: l'affettatura dell'array restituisce un elenco , quindi una sezione dell'array in un contesto scalare restituisce l'elemento finale dell'elenco. La penultima invocazione di foo()stampe 2 perché questo è l'elemento finale nella sezione a due elementi. Cambia in my @array = qw(foo bar baz)e vedrai la differenza. (Per inciso, questo è il motivo per cui non inizializzo array / elenchi su sequenze numeriche basate su 0 o 1 in codice dimostrativo usa e getta. La confusione tra indici, conteggi ed elementi in contesti mi ha morso più di una volta. Sciocco ma vero.)
pilcrow

2
@pilcrow: ho modificato la risposta da utilizzare a b cper chiarire il tuo punto di vista.
Flimm

30

Sono d'accordo con i due poster precedenti. In generale, l'uso $dovrebbe essere evitato. Prototipi sono utili solo quando usando argomenti blocco ( &), glob ( *), o prototipi di riferimento ( \@, \$, \%, \*)


In generale, forse, ma vorrei menzionare due eccezioni: primo, il ($)prototipo crea un operatore unario con nome, che può essere utile (sicuramente Perl li trova utili; anch'io, a volte). In secondo luogo, quando si sovrascrivono i built-in (sia attraverso l'importazione che usando CORE :: GLOBAL: :), in generale dovresti attenersi a qualunque prototipo avesse il built-in, anche se questo include un $, o potresti sorprendere il programmatore (te stesso, anche) con contesto di elenco in cui l'integrato fornirebbe altrimenti un contesto scalare.
Il Sidhekin

4

Alcune persone, guardando un prototipo di subroutine Perl, pensano che significhi qualcosa che non è:

sub some_sub ($$) { ... }

Per Perl, ciò significa che il parser si aspetta due argomenti. È il modo in cui Perl ti consente di creare subroutine che si comportano come built-in, che sanno cosa aspettarsi dal codice successivo. Puoi leggere i prototipi in perlsub

Senza leggere la documentazione, le persone immaginano che i prototipi si riferiscano al controllo degli argomenti in fase di esecuzione o qualcosa di simile che hanno visto in altre lingue. Come la maggior parte delle cose che le persone immaginano su Perl, risultano sbagliate.

Tuttavia, a partire da Perl v5.20, Perl ha una funzionalità, sperimentale mentre scrivo, che dà qualcosa di più simile a ciò che gli utenti si aspettano e cosa. Le firme di subroutine di Perl eseguono il conteggio degli argomenti in fase di esecuzione, l'assegnazione di variabili e le impostazioni predefinite:

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

Questa è la funzionalità che probabilmente desideri se stai considerando i prototipi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.