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