-+  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
In questo articolo verrà approfondita la programmazione condizionale mediante l'introduzione di costrutti alternativi al classico if. Verranno successivamente illustrare le strutture iterative fondamentali di Perl: for, while, until e foreach.

Data di pubblicazione: Questo articolo è stato pubblicato su Dev n°78 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. Eleganza condizionale 

3. Blocchi condizionali 

4. Strutture iterative: for, while, until 

5. Analisi di array ed hash con foreach 

6. Conclusioni 

Introduzione

Nella scorsa lezione sono stati descritti i tipi di dato messi a disposizione da Perl: dalle semplici variabili scalari agli array agli hash. Quanto visto è sufficiente per scrivere qualche semplice programmino. Per scrivere qualcosa di realmente utile è tuttavia necessaria anche la conoscenza delle strutture iterative, quei costrutti che permettono cioè di creare del codice la cui esecuzione è ripetuta un numero di volte fisso oppure determinato da una o più condizioni. Prima di spiegare questi è tuttavia opportuno un approfondimento sulla programmazione condizionale: benché l'istruzione if sia sufficiente per un completo controllo del flusso del codice, essa risulta in molti casi essere poco pratica oppure poco elegante.

 Eleganza condizionale

Perl mette a disposizione svariate sintassi per quanto riguarda la programmazione condizionale: queste permettono di scrivere un codice che assomigli di più alla lingua parlata, il che significa rendere il codice lo più leggibile o completamente incomprensibile, a seconda del criterio con cui si utilizzano tali strutture. Ad esempio l'istruzione if è "ribaltabile", infatti un brano di codice del tipo:

if ($a == 1) { print "a vale 1!\n" }

può tranquillamente diventare:

print "a vale 1!\n" if $a == 1;

In questo caso si possono tranquillamente eliminare le parentesi, in quanto questa seconda sintassi non le richiede. Il "ribaltamento" funziona anche con unless:

print "a vale 1!\n" unless $a != 1;

Al posto di if e di unless si possono usare gli operatori logici. Il primo spezzone di codice può infatti essere scritto anche così:

$a == 1 and print "a vale 1!\n";

oppure così:

$a != 1 or print "a vale 1!\n";

Come si nota le alternative sono tante. Ogni programmatore tende ad utilizzare sempre la stessa forma per la programmazione condizionale, solitamente quella tradizionale. Questo ha il vantaggio di rendere i listati più facilmente comprensibili a chi conosce meno bene il linguaggio o a chi conosce altri linguaggi, ma al contempo rende i programmi più difficili da leggere in quanto la sintassi tradizionale impone sempre l'impiego delle parentesi. In alcuni casi può essere inoltre utile l'operatore ternario "?:", che permette di scrivere un if-else in maniera sintetica al massimo. Poniamo ad esempio di avere:

if ($a eq 'sogno') { print "Stiamo sognando\n" }
else { print "Siamo svegli\n" }

Esso può essere riscritto nella seguente forma:

$a eq 'sogno' ? print "Stiamo sognando\n" : print "Siamo svegli\n";

La condizione a sinistra del punto di domanda viene valutata: se essa è vera, il codice tra il punto di domanda ed i due punti viene eseguito; se al contrario essa è falsa, vengono eseguite le istruzioni poste a destra dei due punti. Questo operatore molto potente e sintetico deriva direttamente dal C: esso ha il vantaggio di permettere di implementare semplici blocchi condizionali scrivendo pochissimo codice, e lo svantaggio di rendere il sorgente più difficile da leggere.

 Blocchi condizionali

Quando i test condizionali sono molti, utilizzare le istruzioni if-elsif-else risulta piuttosto scomodo. Perl, obbedendo anche in questo caso alla massima "c'è più di un modo per ottenere lo stesso risultato", mette naturalmente a disposizione varie alternative. Consideriamo ad esempio il seguente blocco condizionale, scritto utilizzando la forma tradizionale:

if ($a == 1) { print "1!\n" }
elsif ($a == 2) { print "2!\n" }
elsif ($a == 3) { print "3!\n" }
else { print "altro valore!\n" }

Esso può essere scritto, ricordando i costrutti condizionali visti poc'anzi, in maniera più chiara:

SWITCH: {
	print "1!\n", last SWITCH if $a == 1;
	print "2!\n", last SWITCH if $a == 2;
	print "3!\n", last SWITCH if $a == 3;
	print "altro valore!\n";
}

In questo esempio viene creato un blocco di istruzioni che contiene tutti i test condizionali, i quali altro non sono che if ribaltati, già descritti in precedenza.

Ogni riga comporta l'esecuzione di due istruzioni se la condizione risulta essere vera: la stampa a video di una stringa e l'uscita dal blocco SWITCH, tramite last. Se tale istruzione non fosse utilizzata, anche le condizioni successive verrebbero controllate e, soprattutto, l'ultima riga del blocco verrebbe eseguita. Essa infatti rappresenta la (del tutto opzionale) scelta "di default", quella che cioè viene eseguita se tutti i test condizionali precedenti falliscono: di fatto l'ultima riga corrisponde al blocco else di if-elsif-else.

È importante notare che SWITCH rappresenta puramente un nome per il blocco di istruzioni contenuto tra le parentesi graffe: al posto di esso si può usare qualunque altra parola. L'utilizzo di SWITCH in particolare è piuttosto diffuso poiché il risultato di quanto sopra riportato corrisponde a ciò che si può ottenere con l'istruzione switch presente in linguaggi quali ad esempio il C.

All'interno del blocco è possibile utilizzare una qualsiasi delle sintassi condizionali viste nella primissima parte di questo articolo. La scelta dipenderà dal livello di chiarezza che si desidera dare al codice, alla comodità di utilizzo e, non ultimo, alle proprie preferenze personali.

 Strutture iterative: for, while, until

Utilizzando le strutture iterative è possibile ripetere l'esecuzione di un blocco di codice Perl per un certo numero di volte senza doverlo sempre riscrivere. Il numero di iterazioni è essenzialmente determinato da due fattori: esso può essere fisso (cioè predeterminato) oppure arbitrario, deciso dall'avverarsi o meno di una condizione. La struttura iterativa più nota era anche l'unica presente nei primi linguaggi di programmazione. Essa prevede l'impiego dell'istruzione for. Vediamone subito un esempio:

for ($i = 0; $i <= 3; $i++) {
	print "2 x $i = ".(2*$i)."\n";
}

Questo scampolo di codice esegue un certo numero di volte, per la precisione 4, la riga contenuta tra le parentesi graffe. L'output del programma sarà:

2 x 0 = 0
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6

e cioè la stampa degli operandi e del risultato della moltiplicazione di 2 per un numero che va da 0 a 4. Questo numero è il valore della variabile $i, detta variabile di controllo del ciclo; esso cambia ad ogni ripetizione del ciclo, incrementandosi sempre di una unità.

L'istruzione for accetta infatti, tra parentesi, tre parametri: il primo definisce il valore iniziale della variabile di controllo (0 in questo caso), il secondo rappresenta la condizione necessaria per l'esecuzione del blocco di istruzioni interno (il ciclo viene eseguito fino a che tale condizione risulta essere vera, in questo caso finché $i è minore o uguale a tre), il terzo indica invece la variazione da apportare alla variabile di controllo (incremento di una unità in questo esempio). Se si volessero invece saltare tutti i numeri dispari si potrebbe scrivere:

for ($i = 0; $i <= 3; $i = $i+2) {
	print "2 x $i = ".(2*$i)."\n";
}

Se l'incremento che si desidera dare a $i è sempre una unità, esiste una forma più semplice di for, che prevede come parametri il solo numero iniziale e quello finale. Essa prevede l'utilizzo dell'operatore "..", già studiato in relazione agli array.

for $i(0..3) {
	print "2 x $i = ".(2*$i)."\n";
}

Il while è l'altra fondamentale struttura iterativa. Vediamo come trasformare il ciclo for di cui sopra in un blocco while:

$i = 0;
while ($i <= 3) {
	print "2 x $i = ".(2*$i)."\n";
	$i++;
}

La variabile $i va in questo caso impostata al valore iniziale fuori dalla parte iterativa. Tutto ciò che fa while è valutare se una condizione è vera o falsa e continuare ad eseguire il ciclo finché essa risulta essere vera. In questo caso viene utilizzata $i, che però non è una variabile di controllo come nel ciclo for: la sua presenza è del tutto indifferente a while, ed infatti essa va gestita manualmente: il suo incremento in questo caso è ottenuto tramite la riga $i++ inserita nel blocco di istruzioni interno al ciclo iterativo. Come il significato logico di if era ribaltabile con unless, quello di while è a sua volta invertibile utilizzando until. Il codice appena scritto diventerebbe in questo caso:

$i = 0;
until ($i > 3) {
	print "2 x $i = ".(2*$i)."\n";
	$i++;
}

In altre parole l'esecuzione di while termina quando la condizione è falsa, mentre l'esecuzione di until s'interrompe quando la condizione risulta vera. Una forma un tantino diversa di queste due istruzioni permette di far sì che il ciclo venga comunque eseguito una volta prima che la condizione venga testata:

$i = 0;
do {
	print "2 x $i = ".(2*$i)."\n";
	$i++;
} while $i <= 3;

Senza utilizzare il do, se la condizione è falsa in partenza (poniamo che $i sia impostato ad esempio a 10), il ciclo non viene mai eseguito: con do si ha invece almeno una iterazione in ogni caso.

L'esecuzione del blocco all'interno delle strutture iterative può anche essere controllata direttamente da tale codice, senza intervenire sulla condizione, grazie a last, next e redo. La prima istruzione, già vista per quanto riguarda SWITCH, causa l'immediata interruzione dell'esecuzione del ciclo, con conseguente passaggio del controllo alla prima riga posta sotto di esso. Le seconde due causano l'immediata interruzione dell'esecuzione dell'iterazione corrente e la ripresa del ciclo dall'inizio. Tra le due istruzioni c'è una sottile ma importantissima differenza: con next la condizione viene valutata nuovamente, con redo no. In un ciclo for questo comporta che utilizzando la prima istruzione la variabile di controllo venga incrementata, cosa che non avviene se si è invece usato redo. Vediamo qualche riga di esempio: un esempio di tutto questo è visibile in Listato 1.

$ok = 0; # Definita per evitare il loop infinito
for ($i = 0; $i <= 3; $i++) {

	# Salta 0
	if ($i == 0) { next; }

	# Se $i == 2 esce
	if ($i == 2) { last; }

	# Stampa
	print "2 x $i = ".(2*$i)."\n";

	# Se $i == 1 ripete (stampa due volte)
	# a meno che $ok == 1
	if (($i == 1) && ($ok == 0)) { $ok=1; redo; }
}

L'output di questo programma sarà: dunque:

2 x 1 = 2
2 x 1 = 2

Qualcuno avrà a questo punto intuito che è ad esempio possibile implementare un if utilizzando un while. Ad esempio:

if ($a == 1) { print "a vale 1\n"; }

può tranquillamente diventare:

while ($a == 1) { print "a vale 1\n"; last; }

Quanto appena scritto dimostra solamente ancora una volta che ci sono molti metodi per ottenere il risultato che si cerca:, si tratta solo di saper scegliere quello più adatto alla situazione. Sostituire un if con un while non è mai una buona idea: oltre ad essere inelegante, è meno efficiente.

 Analisi di array ed hash con foreach

L'ultima struttura iterativa rimasta da spiegare è foreach, che permette di scandire un array oppure un hash elemento per elemento. Poniamo di aver definito il seguente array:

@luoghi = ('Roma', 'Berlino', 'New York', 'Melbourne');

Come si potrebbe fare a stampare tutti gli elementi dell'array? La soluzione più semplice e più inelegante è quella di servirsi di quattro istruzioni print. Si potrebbe alternativamente utilizzare un ciclo for:

for $i(0..$#luoghi) {
	print "$luoghi[$i]\n";
}

Ricordandosi che $#luoghi contiene l'indice dell'ultimo elemento dell'array, si capisce subito che questo ciclo viene ripetuto tante volte quanti sono gli elementi di @luoghi, stampandone uno per ogni iterazione. L'utilizzo di questa struttura iterativa è una buona soluzione, ma in Perl si può anche scrivere:

foreach $l(@luoghi) {
	print "$l\n";
}

Questa struttura memorizza in $l un elemento alla volta dell'array, e fa sì che le istruzioni nel blocco interno vengano eseguite finché tutta la lista è stata analizzata. Una delle comodità di foreach è quella rendere possibile l'analisi di una copia modificata dell'array, senza per questo alterare l'originale. Ad esempio:

foreach $l(sort @luoghi) {
	print "$l\n";
}

causerà la stampa di tutte le città ordinate alfabeticamente. In realtà @luoghi non viene assolutamente variato: ne viene creata ed utilizzata una copia anonima, contenente gli elementi ordinati. Lo stesso principio permette di analizzare gli hash. Consideriamo il seguente array associativo:

%capitali = qw (Germania  Berlino
                Italia    Roma
                Brasile   Brasilia
                Irlanda   Dublino);

Per stampare tutti i nomi delle nazioni e delle rispettive capitali si può scrivere:

foreach $k(keys %capitali) {
	print "$k: $capitali{$k}\n";
}

L'output sarà formato da quattro righe ciascuna contenente il nome di una nazione seguito dal nome della capitale di essa:

Germania: Berlino
Italia: Roma
Brasile: Brasilia
Irlanda: Dublino

Il programma analizza un array normale contenente le chiavi dell'hash che abbiamo creato e memorizza in $k ciascuna di esse: è poi necessario richiamare l'array associativo originale per la stampa della capitale. Si può anche facilmente ottenere una lista ordinata per nazione:

foreach $k(sort keys %capitali) {
	print "$k: $capitali{$k}\n";
}

La funzione keys() crea un array anonimo contenente le chiavi di %capitali: esso viene passato come parametro a sort(), che inizializza un'altra lista che altro non è che la copia ordinata del primo array anonimo. Anche in foreach si possono tranquillamente utilizzare le istruzioni last, next e redo: esse infatti sono applicabili indiscriminatamente a qualsiasi ciclo iterativo.

 Conclusioni

Programmazione condizionale e strutture iterative sono, come si può facilmente intuire, uno dei fondamentali della programmazione in qualsiasi linguaggio;, quindi, esse vanno imparate molto bene. Scrivendo un programma di media lunghezza si usano di solito decine, forse centinaia, di istruzioni condizionali e svariati cicli iterativi. Viste le cose realmente di base, dalla prossima lezione gli argomenti risulteranno un po' più complicati: verranno infatti introdotti i concetti di funzione, variabili locali e globali e reference. Tra le altre cose l'utilizzo di queste ultime permette, come accennato nella seconda lezione, di creare array ed hash multimensionali.

  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