Indice

Capitolo 6.
Subroutine






   1. Dichiarazione di subroutine

Esistono differenti modi per dichiarare una subroutine:


Dichiarazioni di subroutine Tabella 1
   
sub NOME { corpo } Dichiara la subroutine NOME
   
sub NOME PROTO { corpo } Dichiara la subroutine NOME con i parametri PROTO
   
sub [ PROTO ] { corpo } Dichiara la subroutine ANONIMA
   

Il primo tipo di dichiarazione è il più semplice e quindi quello che scegliamo come punto di partenza nella nostra trattazione. Il NOME della subroutine è il simbolo con il quale ne richiameremo l'esecuzione, il corpo (sempre racchiuso fra parentesi graffe, anche se consta di una sola linea) è l'insieme di istruzioni che la subroutine rappresenta.

     Vediamo un esempio:


Esempio di dichiarazione ed uso di subroutine Fig. 1
 
    1:  #!/usr/bin/perl
    2: 
    3:  sub prova {
    4:    print "Corso di Perl\n";
    5:  }
    6: 
    7:  prova;
 

La dichiarazione alla linea 3 crea una subroutine di nome prova che contiene una singola istruzione (print "Corso di Perl\n";). Alla linea 7 la subroutine viene richiamata. L'output del programma è facilmente prevedibile.

     Fermiamoci a riflettere un momento su come questa subroutine è stata chiamata. Il suo solo nome è bastato perché l'interprete riconoscesse in quel punto una chiamata ad un blocco di codice definito altrove. Tuttavia: è sempre così?

     La risposta è NO! In questo caso la subroutine prova non accetta parametri e quindi costituisce un caso di chiamata dei più semplici. Ma se essa accettasse dei parametri si renderebbe necessario inviarli in maniera non ambigua. (Ricordate che in Perl le parentesi attorno agli argomenti di una sub non sono obbligatorie). Ammettiamo ad esempio che prova venga chiamata in questa maniera: my $int = prova +5;. Il +5 costituisce parametro per la sub prova oppure è un elemento della linea di codice che dovrà essere sommato al risultato di prova? Basta una semplice prova per accorgersi che il compilatore, non sapendo come gestire il valore +5 lo ha passato come parametro alla subroutine. Come dire a Perl che non è un argomento della subroutine? Semplice! Basta: my $int = prova() + 5;! In questo modo la lista di argomenti di prova è stata dichiarata una volta per tutte come una lista nulla.

     L'aggiunta delle parentesi tonde caratterizza una parola come una subroutine. Esiste però un simbolo speciale dedicato alle subroutine: &. Qualsiasi parola preceduta da esso è da considerarsi nome di una subroutine. Perché la necessità di un simbolo speciale per le sub? Per quanto in realtà non strettamente necessario nella più canonica delle programmazioni strutturate (le tonde, abbiamo visto, già bastano a dichiarare una sub), questo simbolo gioca un ruolo importante nella creazione di reference di subroutine. Ma questo lo affronteremo in seguito.


   
2. La gestione degli argomenti

Gli argomenti passati alla sub sono contenuti nell'array @_. Ammettiamo dunque che:


Gestione degli argomenti di una subroutine Fig. 2
 
    1:  #!/usr/bin/perl
    2: 
    3:  sub prova {
    4:    my $first = $_[0];
    5:    my $second = $_[1];
    6: 
    7:    print join ", ", $first, $second;
    8:  }
    9: 
   10:  &prova('elem1', 'elem2');
 

Notate come all'interno della sub, i due argomenti passati siano nelle rispettive posizioni di @_.

     Occorre tuttavia fare molta attenzione alla logica con la quale si passano i parametri. I parametri passati alla subroutine vengono infatti fusi ed "appiattiti" in un unico array. Non è pertanto possibile recuperare due array passati ad una sub separatamente. Vediamo in dettaglio:


Errato passaggio di parametri Fig. 3
 
    1:  #!/usr/bin/perl
    2: 
    3:  sub prova {
    4:    my ( @ar1, @ar2 ) = @_;
    5:    print " --> @ar1\n";
    6:    print " --> @ar2\n";
    7:  }
    8: 
    9:  my @a = (1,2,3);
   10:  my @b = (6,7,8);
   11: 
   12:  prova(@a, @b);
 

Nella sub, @ar1 ingloba tutti i parametri passati, divenendo così (1,2,3,6,7,8), mentre @ar2 è assegnato nullo. Come si risolve questo problema? Passando i parametri per reference!


Corretto passaggio di parametri Fig. 4
 
    1:  #!/usr/bin/perl
    2: 
    3:  sub prova {
    4:    my @ar1 = @{ $_[0] };
    5:    my @ar2 = @{ $_[1] };
    6:    print " --> @ar1\n";
    7:    print " --> @ar2\n";
    8:  }
    9: 
   10:  my @a = (1,2,3);
   11:  my @b = (6,7,8);
   12: 
   13:  prova(\@a, \@b);
 

Notate come la chiamata alla sub passi i valori per reference (usando il backslash davanti). Notate anche come i parametri passati siano degli scalari ( reference ), bisognosi quindi di essere convertiti (typecast) esplicitamente in array (linee 4 e 5).

     Lo stesso ragionamento deve essere tenuto per i valori ritornati dalla sub. I valori in uscita sono appiattiti in una sola lista passata come argomento a return.

     Un metodo molto comodo per gestire gli argomenti in ingresso è la funzione builtin shift. Questa estrae il primo elemento da un array e lo ritorna. Iterate chiamate a shift riducono progressivamente un array fino a svuotarlo. Vediamo come usarla:


Gestione dei parametri con shift Fig. 5
 
    1:  #!/usr/bin/perl
    2: 
    3:  sub prova {
    4:    my @array = @{ shift() };
    5:    my $int = shift;
    6: 
    7:    [ ... ]
    8: 
    9:  }
   10: 
   11:  &prova( \@array, $int );
 

Per ricostruire il primo array passato, prova prima chiama shift (essendo che non le sono forniti valori, questa opera per default su @_). shift recupera uno scalare che è un reference all'array passato. Il typecast esplicito (@{}) che avviene intorno a shift ne trasforma il risultato nell'array richiesto. Infine questo array viene assegnato a @array. Per il secondo parametro è ancora più semplice: essendo uno scalare non richiede typecast esplicito e shift può anche essere chiamata senza parentesi tonde. Attenzione perché questo è un punto che facilmente dà problemi.

     Se chiamate shift senza parentesi tonde (o senza & davanti) Perl sarà confuso circa l'interpretazione da dare a shift. Deve intendersi come una chiamata a funzione o come una parola nuda deprecabilmente scritta senza nessun tipo di quotatura? Perl ricade sul primo caso normalmente, generando però un warning; cosa che viene eliminata dalle tonde in quanto queste tolgono ambiguità all'espressione.


   
3. Subroutine anonime

Una subroutine può anche non avere un nome! Non è un paradosso!! Il costrutto sub { .... } restituisce un reference alla subroutine. Quindi my $func = sub { print "Hello!\n"; } dichiara una sub richiamabile come &$func.

     Una applicazione interessante di questa tecnica è una possibile alternativa al metodo per costruire un costrutto switch.


Emulazione di switch con sub anonime Fig. 6
 
    1:  #!/usr/bin/perl
    2: 
    3:  my %h = (
    4:    valore1 => sub {
    5:      print "valore1\n";
    6:    },
    7: 
    8:    valore2 => sub {
    9:      print "valore2\n";
   10:    },
   11:  );
   12: 
   13:  my $valore = shift();
   14:  if ( defined $h{$valore} ) {
   15:    &{$h{$valore}};
   16:  } else {
   17:    print "Valore non trovato!\n";
   18:  }
 

Ciascun elemento della hash contiene una subroutine anonima da eseguire. La sintassi $h{$valore} ritorna un reference a subroutine. Quindi &{$h{$valore}} esegue la subroutine associata. In questo modo (e senza usare un singolo costrutto logico di test (if oppure &&) avete eseguito uno switch!


   
4. I prototipi

I prototipi servono a descrivere in maniera rigorosa quanti argomenti (e di quale tipo) la sub accetta. Ad esempio sub prova ($$) { ... } specifica che prova accetta solo due parametri scalari ($$). Il vantaggio della prototipizzazione delle sub è che è possibile chiamarle senza parentesi tonde senza che Perl incorra in problemi di interpretazione.

     Ci sono da fare alcune considerazioni sui prototipi. Prima di tutto è bene pensare cosa è giusto prototipizzare. Una subroutine scritta due anni fà non è forse il miglior candidato alla prototipizzazione. Il software scritto fino ad ora che usa quella subroutine potrebbe avere dei problemi con la nuova prototipizzazione. È meglio prototipizzare solo le nuove subroutine.

     Secondo: è necessario ragionare bene sui prototipi per non scrivere cose insensate. Per indicare un array o una hash intesi come reference è necessario prependere il simbolo con un backslash. C'è notevole differenza fra \@ e @: il primo è un array, il secondo è una lista che "consuma" tutti i parmetri rimanenti. Quindi se dovete prototipizzare una sub come func @array, $scalar scrivete sub func (\@$) { ... } come prototipo.


   
5. Pseudo espansione della sintassi attraverso l'uso dei prototipi

L'uso dei prototipi consente una 'pseudo' espansione della sintassi di Perl, con l'aggiunta di nuove parole chiave. Vediamo come:


Pseudo espansione della sintassi con i prototipi Fig. 7
 
    1:  #!/usr/bin/perl
    2: 
    3:  sub try (&$) {
    4:    my ( $try, $catch ) = @_;
    5:    eval { &$try };
    6:    if ( $@ ) {
    7:      local $_ = $@;
    8:      &$catch;
    9:    }
   10:  }
   11: 
   12:  sub catch (&) {
   13:    return $_[0];
   14:  }
   15: 
   16:  try {
   17:    die "Muoio!";
   18:  } catch {
   19:    /Muoio!/ && print "il programma è morto!\n";
   20:  }
 

Come funziona? L'uso dei prototipi di funzione consente di chiamare una sub senza usare il simbolo iniziale di funzione &, e senza usare le parentesi tonde. In più Perl sa quanti parametri passare alla subroutine. Quindi in fase di esecuzione, Perl chiama try. Questa richiede un blocco di codice & e uno scalare generico ($). Quindi passa a try il blocco di codice che segue e lo scalare "che dovrebbe seguire" (ma che non segue). Perl si trova catch, subroutine dichiarata. Quindi invoca catch per recuperarne lo scalare richiesto. Questa ritorna come scalare il blocco di codice che le è stato passato.

     A questo punto try può essere eseguita. Viene valutato il blocco di codice alla linea 5; se $@ (che contiene l'eventuale errore generato dall'esecuzione dell'ultimo eval) contiene qualcosa, allora esegue catch impostando una copia locale di $_ con il contenuto di $@.

     In realtà il prototipo di try è stato dichiarato come (&$) solo per poter consentire l'introduzione di catch fra i due blocchi di codice. L'eliminazione di catch si potrebbe ottenere semplicemente con il prototipo sub try (&&) { .... }, il quale non richiede catch nel mezzo.



Inizio Capitolo Indice