-+  Associazione
-+  Documenti
 |-  Modern Perl
 |-  Bibliografia
 |-  Articoli
 |-  Talk
 |-  Perlfunc
 |-  F.A.Q.
 |-  F.A.Q. iclp
-+  Eventi
-+  Community
-+  Blog
-+  Link
Corso di Perl



 

Versione stampabile.

Michele Beltrame
Lo scopo di questa lezione è quello di introdurre le subroutine, cioè strutture che permettono di "spezzare" il codice in più parti completamente distinte e riutilizzabili. Verrà tuttavia prima spiegato il funzionamento delle reference, cioè particolari variabili che contengono un riferimento ad un'altra variabile o struttura dati. Esse sono utili in moltissimi casi, tra cui il passaggio di parametri alle subroutine.

Data di pubblicazione: Questo articolo è stato pubblicato su Dev n°79 e in questo sito per autorizzazione espressa del "Gruppo Editoriale Infomedia" (http://www.infomedia.it).

© 2007 Michele Beltrame. Michele Beltrame vive a Maniago, PN, è programmatore Perl e convinto sostenitore del movimento Open Source. Può essere raggiunto via e-mail all'indirizzo mb@italpro.net

1. Introduzione 

2. Introduzione alle reference 

3. Reference ad array ed hash 

4. Strutture dati multimensionali 

5. Subroutine 

6. Variabili locali 

7. Parametri 

8. Conclusioni 

Introduzione

Per programmi di una certa complessità è quasi obbligatorio, sia per esigenze di efficienza che di chiarezza, spezzare il proprio codice in più sezioni distinte, richiamabili l'una dall'altra. Spesso è anche opportuno dividere il proprio codice in più file, cosa possibilissima e che verrà spiegata nelle lezioni successive. Subroutine e funzioni, che in Perl sono praticamente la stessa entità, permettono appunto queste divisioni. Prima di addentrarci nel loro studio è tuttavia opportuno analizzare il funzionamento delle reference, che ritorneranno utili sia per quanto riguarda le subroutine che in molti altri casi.

 Introduzione alle reference

Scopriamo brevemente cosa sono e come funzionano le reference. Come è già noto da alcune lezioni, Perl mette a disposizione più tipi di variabili. Consideriamo una variabile scalare: essa contiene un numero oppure una stringa, ad esempio:

$a = 'bored';

Essa può essere direttamente modificata, stampata e via dicendo. È inoltre possibile creare un riferimento ad essa, cioè una seconda variabile che "punti" a $a ma che non sia $a. In altri linguaggi ed in altri contesti tali riferimenti sono chiamati handle: questo nome, che significa maniglia, rende probabilmente meglio l'idea di cosa si tratti: in effetti tali variabili sono delle maniglie tramite le quali è possibile accedere alla variabile originale ed al suo valore, leggendolo oppure modificandolo. Se volessimo creare una reference $ra a $a potremmo scrivere:

$ra = \$a;

Il backslash (\) ritorna una reference alla variabile posta alla sua destra, valore che poi viene memorizzato in $ra. Se noi a questo punto scriviamo:

print $ra;

non otteniamo, come qualcuno potrebbe immaginare, la stampa del valore originale della variabile, bensì qualcosa del tipo:

SCALAR(0x80d1fbc)

La prima parte indica il tipo di variabile a cui la reference "punta". I vari tipi possibili sono visibili in tabella 1:

SimboloSignificato
SCALARVariabile scalare
ARRAYArray
HASHHash (array associativo)
CODECodice Perl (ad esempio una subroutine)
GLOBTypeglob
REFLa reference punta ad un'altra reference, che a sua volta poi punterà ad altro

alcuni di essi non risulteranno chiari a questo punto del corso, ma lo saranno in futuro. La seconda parte della stringa visualizzata indica invece l'indirizzo della variabile puntata: la reference in questo caso si riferisce al valore memorizzato in 0x80d1fbc. Per conoscere il tipo di una variabile puntata da una reference è anche possibile utilizzare la funzione ref(), che ritorna una stringa come quelle di tabella 1, omettendo l'indirizzo. Le reference si possono assegnare e copiare esattamente come le variabili normali:

$ra2 = $ra;
$ra3 = \$a;

$ra2 e $ra3 sono esattamente uguali a $ra, in quanto la prima è stata copiata direttamente da $ra e la seconda è stata creata a partire dalla stessa variabile scalare. È importante notare che non è stato copiato il contenuto di $a, del quale esiste ancora una sola copia. Cancellare o modificare una delle tre reference create non altera assolutamente il valore della variabile originale. Ma quindi come si può accedere alla variabile puntata? "Dereferenziando", come nel seguente esempio:

print $$ra;

Osserviamo bene quanto scritto, che si potrebbe anche porre nella forma:

print ${$ra};

Tra parentesi graffe è indicata la reference, il cui valore è poi passato ad un secondo dollaro ($), che indica che il valore da stampare è un altro scalare. L'output è prevedibilmente:

bored

L'assegnazione di un valore alla variabile puntata avviene esattamente secondo le stessa modalità:

$$ra = 'dark';

 Reference ad array ed hash

Anche ad array ed hash è possibile creare reference, utilizzando esattamente lo stesso metodo valido per gli scalari. La seguenti due righe creano, nell'ordine, una reference ad un array ed una ad un hash, dopo averli definiti:

@a = (1, 2, 3);
%b = ('a' => 'Bianco', 'b' => 'Grigio');
$ra = \@a;
$rb = \%b;

La sintassi usata è, come si vede, esattamente quella vista in precedenza, con la differenza che accanto al backslash, nella riga che definisce la reference, va riportato il simbolo dell'array oppure quello dell'hash (rispettivamente @ e %). È anche possibile creare una reference ad un array oppure ad un hash anonimo, cosicché non sia necessario definirlo prima e costruire l'handle poi:

$ra = [1, 2, 3];
$rb = {'a' => 'Bianco', 'b' => 'Grigio'};

La differenza sostanziale con la definizione di un array o di un hash normale è che, mentre nel primo caso si utilizzano sempre le parentesi tonde, qui vanno impiegate rispettivamente quelle quadre e quelle graffe. Utilizzando questa sintassi avremo $ra che punta ad un array anonimo (non accessibile direttamente tramite @a) e $rb che punta ad un hash anonimo. Per accedere alla struttura dati si può procedere come visto per gli scalari, ad esempio si può scrivere:

# Stampa il numero di elementi dell'array puntato da $ra
print scalar @$ra;
# Stampa il numero di chiavi dell'hash puntato da $rb
print scalar keys %$rb;

L'utilizzo di scalar, già visto nelle precedenti lezioni, è indispensabile per far sì che venga stampato il numero di elementi: se non venisse forzato un contesto scalare verrebbe visualizzata la lista degli elementi dei due array. Per accedere agli elementi di un array oppure di un hash utilizzando una sua reference si utilizza:

print $$ra[1];
print $$rb{'b'};

Perl mette naturalmente a disposizione un'altra sintassi, più simile a quella del C++, per dereferenziare array ed hash:

print $ra->[1];
print $rb->{'b'};

Benché il primo metodo sia il più utilizzato (il risparmio di un carattere è determinante per il pigro programmatore Perl), senza dubbio il secondo è consigliabile in quanto più chiaro e leggibile.

 Strutture dati multimensionali

Un utilizzo immediato delle reference ad array ed hash è quello che porta alla creazione di array multimensionali, hash multimensionali, array di hash e hash di array. Ciascun elemento di un array o di un hash può infatti contenere una reference: se in particolare esso contiene un handle ad un'altra struttura dati, ecco allora che si capisce come sia possibile ottenere la multidimensionalità. In listato 1 sono mostrati quattro esempi, ciascuno dei quali definisce una delle strutture dati di cui sopra. Come si nota si va a creare sempre un semplice array o hash, e non una reference ad esso: è all'interno delle parentesi tonde che vengono definite, come elementi, più reference. Accedere agli elementi è piuttosto semplice, ed è possibile farlo anche senza prima definire la struttura dati: in Perl infatti, come noto, non è necessario dichiarare le variabili, ed è quindi possibile assegnare un valore ad esse senza altre istruzioni. In questo caso, rifacendoci ai quattro esempi del riquadro, potremmo scrivere:

$persone[1][1] = "402, Cedars Drive";
$persone2[1]{'indirizzo'} = "402, Cedars Drive";
$persone3{'USA'}[1] = "402, Cedars Drive";
$persone4{'USA'}{'indirizzo'} = "402, Cedars Drive";

È dunque sufficiente specificare gli indici (o le chiavi nel caso si tratti di hash) da sinistra verso destra, a partire da quello più "esterno". Qualcuno potrebbe chiedersi: ma non serve dereferenziare per accedere al valore puntato dalle reference interne alla struttura dati? In realtà si, ma Perl, nel caso di strutture multidimesionali, permette di "dimenticarselo" e lo fa in automatico. Di fatto scrivere:

$persone[1][1] = "402, Cedars Drive";

è solo un modo più pratico per scrivere:

${$persone[1]}[1] = "402, Cedars Drive";

oppure:

$persone[1]->[1] = "402, Cedars Drive";

 Subroutine

Le subroutine sono delle strutture, messe a disposizione da tutti i linguaggi di programmazione, che, come anticipato prima, permettono di spezzare il codice in più blocchi completamente distinti, e richiamabili uno dall'altro. Tutto questo ha il vantaggio di rendere il codice più chiaro e, soprattutto, di scrivere parti riutilizzabili, cioè righe di codice che possono essere richiamate un numero indefinito di volte da altri punti del proprio programma. Supponiamo di voler scrivere del codice che stampi alcune stringhe anteponendo ad esse "Nome: ":

print "Nome: Markus\n";
print "Nome: Michele\n";
print "Nome: Annie\n";

Se noi ora volessimo cambiare "Nome: " con "Persona: " ci troveremmo a dover intervenire sul codice in tre punti. Tra le varie alternative a questo, c'è quella che prevede l'utilizzo di una subroutine:

sub pnome {
  my ($nome) = shift;
  print "Nome: $nome\n";
}

&pnome("Markus");
&pnome("Michele");
&pnome("Annie");

La parola chiave sub è quella che definisce la subroutine, che si chiama in questo caso pnome(): il codice che la compone va racchiuso tra parentesi graffe. Essa è poi richiamata tre volte dal codice principale, tramite le righe &pnome(): la "e commerciale" è il carattere che indica all'interprete che il nome passato sulla destra è codice, si tratta cioè di una subroutine. Tra parentesi viene passato, come parametro, il nome da stampare. Esso viene poi memorizzato in $nome nella subroutine dalla riga:

my ($nome) = shift;

Ma perché viene utilizzato shift? I parametri passati alle subroutine vengono inseriti nell'array @_: visto che questo è il nome della variabile "di default" di Perl, non serve passarlo come parametro a shift(). Volendo avremmo potuto scrivere, in maniera più completa:

my ($nome) = shift @_;

Come visto in una delle precedenti puntate, shift() ritorna il primo (ed in questo caso unico) elemento dell'array, rimuovendolo dalla lista. I parametri passati possono essere un numero arbitrario:

&pnome("primo", "secondo", "terzo");

e possono essere recuperati dall'array ad esempio con:

my ($p1, $p2, $p3) = split;

Anche in questo caso il parametro a split() è opzionale, in quanto aggiunto automaticamente dall'interprete. A questo punto qualcuno si starà però chiedendo cosa sia quel my...

 Variabili locali

Le variabili in Perl sono, se non diversamente specificato, globali: è cioè possibile accedere al loro contenuto da qualsiasi parte del programma, sia dal corpo principale che dalle varie subroutine. Si potrebbero utilizzare sempre le variabili globali, ma spesso è consigliabile "localizzarne" l'uso, sia per ragioni di efficienza che per ragioni di praticità. La parola chiave my permette di definire delle variabili valide solo all'interno del blocco di istruzioni corrente, cioè racchiuso tra parentesi graffe. Alla fine dell'esecuzione di tale blocco queste variabili vengono distrutte, e la memoria precedentemente allocata per esse liberata. Un altro effetto di my di cui è necessario tenere conto è il fatto che essa maschera un'eventuale variabile globale con lo stesso nome. Chiariamo con un esempio:

sub mysub {
  my ($a) = 1;
  print  "$a\n";
}

$a = 2;
print "$a\n";
&mysub();
print "$a\n";

L'output di questo programma è:

2
1
2

Il valore della variabile globale $a non viene quindi cambiato, poiché my crea una variabile con lo stesso nome, ma completamente distinta e valida solamente all'interno della subroutine. Se non avessimo utilizzato my ma solamente:

$a = 1;

il valore della variabile globale sarebbe effettivamente stato cambiato e l'output sarebbe di conseguenza stato:

2
1
1

Si può subito intuire l'estrema utilità di questa caratteristica: è possibile infatti scrivere subroutine utilizzando variabili con nomi qualsiasi, senza preoccuparsi di sceglierle in modo che non coincidano con altre già utilizzate nel programma.

 Parametri

Abbiamo visto, nella prima subroutine analizzata, come si fa a passare uno o più parametri. Consideriamo nuovamente una subroutine di questo tipo:

sub incrementa {
  my ($num) = shift;
  $num++;
}

Questo (inutile) codice incrementa semplicemente di una unità il parametro. Se eseguiamo la chiamata come di seguito indicato:

$mionumero = 4;
&incrementa($mionumero);
print $mionumero;

ci ritroviamo con un $num che vale sempre 4, anche dopo l'incremento. La ragione di questo è abbastanza chiara: Il parametro passato viene copiato in $num, che è una variabile locale della funzione: è quindi questa ad essere modificata, e non quella esterna. Anche ponendo che variabile esterna ed interna avessero avuto lo stesso nome, la sostanza non sarebbe cambiata, per le motivazioni viste poc'anzi. Avremmo potuto risolvere togliendo il my ed utilizzando semplicemente il seguente programma:

sub incrementa {
  $mionumero++;
}

$mionumero = 4;
&incrementa();
print $mionumero;

Questo, benché funzionante, è molto limitativo: se noi avessimo avuto più di una variabile da incrementare, avremmo dovuto scrivere tante subroutine quante erano tali variabili. Si intuisce subito l'inefficienza e la scarsa praticità del tutto. Perl consente tuttavia di modificare il valore dei parametri, a patto che anziché passare direttamente la variabile alla funzione ne venga passata una reference. Vediamo un esempio:

sub incrementa {
  my ($rnum) = shift;
  $$rnum++;
}

$mionumero = 4;
&incrementa(\$mionumero);
print $mionumero;

La chiamata a incrementa() prevede in questo caso, tramite l'uso del backslash (\) la creazione "al volo" di una reference a $mionumero ed il passaggio di questa alla subroutine. All'interno di quest'ultima, tale reference viene memorizzata in $rnum e successivamente dereferenziata ed incrementata con $$rnum++. L'output sarà correttamente 5.

 Conclusioni

Abbiamo iniziato a capire come rendere il proprio codice più chiaro, efficiente e soprattutto riutilizzabile tramite l'impiego delle subroutine, che portano ad un tipo di programmazione più "strutturata". A questo punto inizia inoltre ad apparire chiara l'utilità delle reference: esse sono indispensabili anche per il passaggio di parametri non scalari come hash ed array, argomento che studieremo nella prossima lezione. Analizzeremo inoltre le funzioni, cioè delle subroutine che ritornano un valore.

  1. Ellen Siever, Stephen Spainhour, Nathan Patwardhan - "Perl in a Nutshell", O'Reilly & Associates, Gennaio 1999
  2. Larry Wall, Tom Christiansen, Randal L. Schwartz - "Programming Perl (2nd edition)", O'Reilly & Associates, Settembre 1996
  3. Jon Orwant, Jarkko Hietaniemi, John Macdonald - "Mastering algorithms with Perl", O'Reilly & Associates, Agosto 1999

D:
Progetti e documenti in rilievo
Corso di Perl Progetto pod2it
D:
La ML di Perl.it
mongers@perl.it è la lista ufficiale di Perl Mongers Italia per porre quesiti di tipo tecnico, per rimanere aggiornato su meeting, incontri, manifestazioni e novità su Perl.it.
Iscriviti!
D:
Annunci Google