=head1 NOME Il brevissimo tutorial di Mark a proposito dei riferimenti =head1 Descrizione Una delle piE importanti nuove caratteristiche di Perl 5 era la capacitE di gestire strutture dati complesse come gli array o le hash annidate. Per consentirlo, Perl 5 ha introdotto una nuova feature chiamata 'riferimento', e l'uso dei riferimenti E la chiave per gestire, con Perl, dati complessi e strutturati. Sfortunatamente, c'E un sacco di sintassi bizzarra da imparare, e la pagina principale del manuale puE risultare difficile da seguire. Il manuale E piuttosto completo, e a volte la gente trova che questo sia un problema, poichE puE essere difficile comunicare cosa E importante e cosa non lo E. Fortunatamente, avete bisogno solo di un 10% di quanto viene detto nella pagina principale, per avere il 90% del beneficio. Questa pagina vi mostrerE quel 10%. =head1 Chi ha bisogno di strutture dati complesse? Un problema che saltava fuori spesso con Perl 4 era come rappresentare una hash i cui valori fossero liste. Perl 4 aveva le hash, naturalmente, ma i valori dovevano essere scalari, non potevano essere liste. PerchE si dovrebbe volere una hash di liste? Prendiamo un semplice esempio: avete un file contenente nomi di cittE e nomi di paesi, come questo: Chicago, USA Francoforte, Germania Berlino, Germania Washington, USA Helsinki, Finlandia New York, USA E volete produrre un output come queste, con ogni paese menzionato una sola volta, e poi una lista delle cittE presenti in quel paese, in ordine alfabetico: Finlandia: Helsinki. Germania: Berlino, Francoforte. USA: Chicago, New York, Washington. La maniera naturale per fare ciE E avere una hash le cui chiavi siano i nomi dei paesi. Associata ad ogni nome di paese c'E una lista con le cittE presenti in quel paese. Ogni volta che si legge una linea dell'input, la si spezza (C) in un paese e in una cittE, si consulta la lista di cittE giE registrate come appartenenti ad un paese, e si appende la nuova cittE alla lista. Una volta finito con l'input, si itera sulla hash come al solito, ordinando ogni lista di cittE prima di stamparla. Se i valori della hash non possono essere liste, avete perso. In Perl 4, i valori delle hash non possono essere liste; possono soltanto essere stringhe. Avete perso. Dovreste probabilmente combinare in qualche modo tutti i nomi di cittE in una singola stringa, e poi, quando viene il momento di scrivere l'output, dovreste spezzare la stringa in una lista, ordinare la lista e ritrasformarla in una stringa. E un lavoraccio, e soggetto ad errori. Ed E frustrante, poichE il Perl ha giE le liste, del tutto adatte, che potrebbero risolvere il problema se soltanto poteste usarle. =head1 La soluzione Nel tempo in cui Perl 5 nasceva, eravamo giE impantanati in questo design: i valori delle hash devono essere scalari. La soluzione sono i riferimenti. Un riferimento E un valore scalare che I ad un intero array oppure ad un'intera hash (o a qualunque altra cosa). I nomi sono tipi di riferimento con cui probabilmente avete giE familiaritE. Pensate al Presidente degli Stati Uniti: una complicata e scomoda massa di carne e ossa. Ma per parlare di lui, o per rappresentarlo in un programma, tutto ciE di cui c'E bisogno E la facile e comoda stringa "George Bush". I riferimenti in Perl sono come i nomi per gli array e le hash. Sono i nomi interni, privati, del Perl, quindi potete stare sicuri che non sono ambigui. A differenza di "George Bush", una reference fa riferimento ad una cosa sola, e sapete sempre a cosa si riferisce. Se avete un riferimento ad un array, da esso potete ottenere l'intero array. Se avete un riferimento ad una hash, potete ottenere l'intera hash. Ma il riferimento rimane un semplice scalare. Non potete avere una hash i cui valori siano array; i valori in una hash possono solo essere scalari. Da qui non ci smuoviamo. PerE un singolo riferimento puE referenziare un intero array, e i riferimenti sono scalari, quindi potete avere una hash di riferimenti ad array, e sarE utile quanto una hash di array. Torneremo al problema paesi-cittE piE tardi, dopo che avremo visto un po' di sintassi per gestire i riferimenti. =head1 Sintassi Ci sono solo due modi per costruire un riferimento, e solo due modi per usarne uno una volta che lo avete. =head2 Costruire riferimenti =head3 B Se mettete un C<\> di fronte alla variabile, ottenete un riferimento a quella variabile. $arif = \@array; # $arif ora contiene un riferimento ad @array $hrif = \%hash; # $hrif ora contiene un riferimento ad %hash Una volta che il riferimento E memorizzato in una variabile come $aref o $href, potete copiarlo o memorizzarlo cosE come qualunque altro scalare. $xy = $aref; # $xy contiene una reference ad @array $p[3] = $href; # $p[3] contiene una reference ad %hash $z = $p[3]; # $z cotiene una reference ad %hash Questi esempi mostrano come costruire riferimenti a variabili usandone i nomi. A volte puE capitare che vogliate fare un array o una hash senza nome. E un caso analogo all'usare la stringa "\n" o il numero 80 senza che esso sia memorizzato prima in una variabile. =head3 B C<[ ELEMENTI ]> crea un nuovo array anonimo, e restituisce un riferimento ad esso. C<{ ELEMENTI }> crea una nuova hash anonima, ritornando un riferimento a tale hash. $aref = [1, "pippo", undef, 13]; # $aref contiene un riferimento all'array $href = { APR => 4, AUG => 8 }; # $href contiene un riferimento alla hash I riferimenti ottenuti nel secondo modo sono dello stesso tipo di quelli ottenuti nel primo modo: # Questo... $aref = [1, 2, 3]; # ... ha lo stesso effetto di questo @array = (1, 2, 3); $aref = \@array; La prima linea E un'abbreviazione delle altre due, eccetto per il fatto che non crea la variabile C<@array>, che E superflua. Se scrivete semplicemente C<[]> ottenete un nuovo array anonimo, vuoto. Se scrivete semplicemente C<{}> ottenete una nuova hash anonima, vuota. =head2 Usare i riferimenti Che cosa si puE fare con un riferimento una volta che ne avete uno? E un valore scalare, e abbiamo visto che potete memorizzarlo come uno scalare, e ripescarlo esattamente come uno scalare. Ma ci sono un altro paio di modi per usarlo: =head3 B Se C<$aref> contiene un riferimento ad un array, allora si puE mettere C<{$aref}> ovunque avreste normalmente messo il nome di un array. Ad esempio, C<@{$aref}> anzichE C<@array>. Ecco alcuni esempi: Array: @a @{$aref} Un array reverse @a reverse @{$aref} Inverte l'array $a[3] ${$aref}[3] Un elemento dell'array $a[3] = 17; ${$aref}[3] = 17 Assegnare un elemento In ogni linea ci sono due espressioni che fanno la stessa cosa. La versione a sinistra opera sull'array C<@a>, mentre la versione a destra opera sull'array che E referenziato da C<$aref>, ma una volta che hanno trovato l'array sul quale stanno lavorando, faranno le stesse cose ai rispettivi array. L'uso di un riferimento ad hash E esattamente la stessa cosa: %h %{$href} Una hash keys %h keys %{$href} Prende le chiavi dalla hash $h{'red'} ${$href}{'red'} Un elemento della hash $h{'red'} = 17 ${$href}{'red'} = 17 Assegna un elemento Qualsiasi cosa vogliate fare con un riferimento, il B vi dice come farlo. E sufficiente che scriviate il codice Perl che avreste scritto per fare la stessa identica cosa con un normale array o hash, e poi sostituire C<{$reference}> al nome dell'array o dell'hash. "Come faccio un ciclo su un array quando tutto quel che ho E un riferimento?" Beh, per fare un ciclo su un array regolare, dovreste scrivere for my $elemento (@array) { ... } quindi rimpiazzate il nome dell'array, C, con il riferimento fra parentesi graffe: for my $elemento (@{$riferimento_ad_array}) { ... } "Come faccio a stampare il contenuto di una hash quando tutto ciE che ho E un riferimento?" Prima scrivete il codice per stampare una hash generica: for my $chiave (keys %hash) { print "$chiave => $hash{$chiave}\n"; } e successivamente rimpiazzate il nome della hash con il riferimento fra parentesi graffe: for my $chiave (keys %{$riferimento_ad_hash}) { print "$chiave => ${$riferimento_ad_hash}{$chiave}\n"; } =head3 B Il B di utilizzo E tutto ciE di cui avete veramente bisogno, perchE vi dice come fare qualunque cosa di cui possiate mai avere bisogno. Ma l'operazione piE comune su un array o su una hash E l'estrazione di un singolo elemento, e la sintassi del B E involuta. Per questo motivo esistono delle abbreviazioni. C<${$aref}[3]> E troppo difficile da leggere; al suo posto potete usare C<< $aref->[3] >>. C<${$href}{red}> E troppo difficile da leggere; al suo posto potete usare C<< $href->{red} >>. Se C<$aref> E un riferimento ad array, allora C<< $aref->[3] >> E il quarto elemento dell'array. Non confondetelo con $aref[3], che E il quarto elemento di un array del tutto diverso, ingannevolmente chiamato C<@aref>. C<$aref> e C<@aref> non hanno alcun rapporto tra loro, cosE come non lo hanno C<$item> e C<@item>. Analogamente, C<< $href->{'red'} >> E parte della hash referenziata dalla variabile scalare C<$href>, forse addirittura di una hash anonima. C<$href{'red'}> E parte della hash dall'ingannevole nome C<$href>. E facile dimenticarsi il C<<->>>, e se lo fate, otterrete bizzarri risultati quando il vostro programma prenderE elementi da array ed hash totalmente inaspettati, che non sono quelli che volevate usare. =head2 Un esempio Vediamo un esempio di come questo sia utile. Per prima cosa, ricordate che C<[1, 2, 3]> crea un array anonimo contenente C<(1, 2, 3)> e restituisce una reference a tale array. Ora pensate a @a = ( [1, 2, 3], [4, 5, 6], [7, 8, 9] ); C<@a> E un array di tre elementi, ognuno dei quali E un riferimento ad un altro array. C<$a[1]> E uno di questi riferimenti. Referenzia un array, l'array contenente C<(4, 5, 6)>, e poichE E un riferimento ad un array, la B<> dice che possiamo scrivere C<< $a[1]->[2] >> per ottenere il terzo elemento da tale array. C<< $a[1]->[2] >> E 6. Allo stesso modo, C<< $a[0]->[1] >> E 2. Quello che abbiamo E come un array bidimensionale; si puE scrivere C<< $a[RIGA]->[COLONNA] >> per avere (o modificare) l'elemento nella riga e colonna desiderate. La notazione pare ancora poco maneggevole, quindi c'E ancora un'altra abbreviazione. =head2 La regola della freccia Tra due B, la freccia E opzionale. Al posto di C<< $a[1]->[2] >>, si puE scrivere C<$a[1][2]>; vuol dire la stessa cosa. AnzichE C<< $a[0]->[1] >>, si puE scrivere C<$a[0][1]>; E la stessa cosa. Ora sembrano davvero array bidimensionali! Potete rendervi conto del perchE le freccie sono importanti. Senza di esse, dovremmo scrivere C<${$a[1]}[2]> anzichE C<$a[1][2]>. Per array tridimensionali, la freccia consente di scrivere $x[2][3][5] anzichE C<${${$x[2]}[3]}[5]>: illeggibile. =head1 La soluzione Ed ecco la risposta al problema che avevo posto prima, riformattare un file contenente nomi di cittE e paesi. 1 my %tabella; 2 while (<>) { 3 chomp; 4 my ($citta, $nazione) = split /, /; 5 $tabella{$nazione} = [] unless exists $tabella{$nazione}; 6 push @{$tabella{$nazione}}, $citta; 7 } 8 foreach $nazione (sort keys %tabella) { 9 print "$nazione: "; 10 my @le_citta = @{$tabella{$nazione}}; 11 print join ', ', sort @le_citta; 12 print ".\n"; 13 } Il programma E costituito da due parti. Le linee 2--7 leggono l'input e costruiscono la struttura dati, e le linee 8--13 analizzano i dati e stampano il rapporto. Otterremo una hash, C<%tabella>, le cui chiavi sono i nomi delle nazioni, ed i cui valori sono riferimenti ad array contenenti i nomi delle cittE. La struttura dati avrE piE o meno questo aspetto: %tabella +-----------+---+ | | | +-------------+---------+ | Germania | *---->| Francoforte | Berlino | | | | +-------------+---------+ +-----------+---+ | | | +----------+ | Finlandia | *---->| Helsinki | | | | +----------+ +-----------+---+ | | | +---------+------------+----------+ | USA | *---->| Chicago | Washington | New York | | | | +---------+------------+----------+ +-----------+---+ Per cominciare, ci concentreremo sull'uscita. Supponendo di avere giE questa struttura dati, come la stampiamo? 8 foreach $nazione (sort keys %tabella) { 9 print "$nazione: "; 10 my @le_citta = @{$tabella{$nazione}}; 11 print join ', ', sort @le_citta; 12 print ".\n"; 13 } C<%tabella> E una comune hash, da cui otteniamo la lista delle chiavi, le ordiniamo e facciamo un ciclo su di esse, come al solito. L'unico utilizzo di un riferimento E alla riga 10. C<$tabella{$nazione}> cerca la chiave C<$nazione> nella hash e trova il valore corrispondente, che E un riferimento ad un array di cittE in quella determinata nazione. Il B ci dice che possiamo arrivare all'array con l'espressione C<@{$tabella{$nazione}}>. La riga 10 E del tutto analoga a @le_citta = @array; eccetto che il nome C E stato rimpiazzato dal riferimento (fra parentesi graffe) C<{$table{$country}}>. Il sigillo C<@> dice a Perl di prendere l'intero array. Una volta presa la lista delle cittE, la ordiniamo e la stampiamo come di consueto. Le righe 2--7 hanno la responsabilitE di costruire la struttura dati di partenza. Ecco di nuovo i riferimenti: 2 while (<>) { 3 chomp; 4 my ($citta, $nazione) = split /, /; 5 $tabella{$nazione} = [] unless exists $tabella{$nazione}; 6 push @{$tabella{$nazione}}, $citta; 7 } Le righe 2--4 acquisiscono il nome di una cittE e della nazione in cui si trova. La riga 5 verifica se il nome della nazione E giE presente come chiave nella hash. Se non E cosE, il programma utilizza la notazione C<[]> (in linea con il B di generare un riferimento ad array) per costruire un nuovo array anonimo (e vuoto) per contenere i nomi delle cittE, e installa un riferimento a questo array nella hash, sotto la chiave corrispondente alla nazione. La riga 6 isntalla il nome della cittE nell'array piE appropriato. C<$tabella{$nazione}> ora contiene un riferimento all'array delle cittE viste fino ad ora nella determinata nazione. La riga 6 E esattamente come push @array, $citta; ad eccezione del fatto che il nome C E stato sostituito dal riferimento fra parentesi graffe C<{$tabella{$nazione}}>. La chiamata alla funzione C aggiunge il nome della cittE in fondo all'array "puntato" dal riferimento. Ho saltato un aspetto sottile. La riga 5 non E strettamente necessaria, e possiamo sbarazzarcene. 2 while (<>) { 3 chomp; 4 my ($citta, $nazione) = split /, /; 5 #### $tabella{$nazione} = [] unless exists $tabella{$nazione}; 6 push @{$tabella{$nazione}}, $citta; 7 } Se giE esiste un elemento in C<%tabella> per la C<$nazione> corrente non cambia niente. La riga 6 individuerE il valore in C<$tabella{$nazione}>, che E un riferimento ad un array, ed aggiungerE il nome della cittE in coda all'array stesso. Ma cosa fa quando C<$nazione> contiene il nome di una chiave, poniamo C, che ancora non esiste in C<%tabella>? Stiamo parlando di Perl, per cui fa la cosa giusta. In particolare, Perl si accorge che volete inserire C in un array che non esiste, e si rende utile creando un nuovo array anonimo e vuoto al posto vostro, installandolo all'interno di C<%tabella>; successivamente, effettua l'inserimento di C. Questo comportamento E chiamato `autovivificazione'--portare le cose in vita automaticamente. Perl nota che la chiave non E presente nella hash, per cui crea un nuovo elemento automaticamente. Per osserva che volete utilizzare il valore contenuto nella hash come se fosse un riferimento ad array, per cui crea un nuovo array (vuoto) ed installa un riferimento ad esso all'interno della hash, automaticamente. E, come di consueto, Perl allunga l'array di una posizione, per tenere traccia del nuovo nome di cittE. =head1 Il Resto Ho promesso di darvi il 90% dei benefici con il 10% dei dettagli. Il che vuol dire che ho tralasciato il 90% dei dettagli. Ora che avete una panoramica delle parti importanti, dovrebbe risultare facile la lettura di L, che parla del 100% dei dettagli. Alcuni punti importanti di L: =over 4 =item * Si possono costruire riferimenti a qualunque cosa, compresi scalari, funzioni e altri riferimenti. =item * Nel B di costruzione, si possono omettere le parentesi graffe quando ciE che contengono E una variabile scalare atomica come C<$aref>. Ad esempio, C<@$aref> E uguale a C<@{$aref}>, e C<$$aref[1]> E lo stesso che C<${$aref}[1]>. Se state cominciando, magari potreste adottare l'abitudine di usare sempre le parentesi graffe. =item * Quanto segue non effettua una copia dell'array sottostante: $rif_array2 = $rif_array1; Tutto ciE che ottenete sono due riferimenti allo stesso array. Se modificate C<< $rif_array1->[23] >> vedrete che anche C<< $rif_array2->[23] >> E cambiato. Per copiare l'array, usate $rif_array2 = [@{$rif_array1}]; In questo modo viene utilizzata la notazione C<[...]> per creare un nuovo array anonimo, un riferimento al quale viene assegnato a C<$rif_array2>. Il nuovo array E inizializzato con il contenuto dell'array riferito da C. Similmente, per copiare una hash anonima potete utilizzare $rif_hash2 = {${$rif_hash1}}; =item * Per vedere se una variabile contiene un riferimento, si puE usare la funzione `ref'. Restituisce un valore vero se l'argomento E un riferimento. In effetti, E ancora meglio: restituisce la stringa C per i riferimenti ad hash, e la stringa C per i riferimenti ad array. =item * Se provate ad usare un riferimento come una stringa, otterrete stringhe quali ARRAY(0x80f5dec) o HASH(0x826afc0) Se vi dovesse mai capitare di vedere una stringa simile, sappiate di aver stampato per sbaglio un riferimento. Un effetto collaterale di questa rappresentazione E il fatto che si puE usare C per vedere se due riferimenti referenziano la stessa cosa (ma E meglio usare C<==>, che E piE veloce.) =item * Potete usare una stringa come se fosse un riferimento. Se usate la stringa "foo" come un riferimento ad array, E trattato come un riferimento all'array C<@foo>. E chiamato I o I. La dichiarazione C [o anche C e basta, NdT] disabilita questa caratteristica, che puE causare ogni sorta di guaio se la usate per sbaglio. =back Forse preferirete proseguire con L, anzichE L; parla, in dettaglio, di liste di liste e di array multidimensionali. Poi, potreste passare a L; E il "Ricettario delle Strutture Dati", che mostra le ricette per stampare array di hash, hash di array e altri tipi di dato. =head1 In sintesi Ognuno ha bisogno di strutture dati composte, e in Perl il modo di ottenerle sono i riferimenti. Ci sono quattro regole importanti per gestire i riferimenti. Due per creare riferimenti e due per usarli. Una volta imparate queste regole potete fare la maggior parte delle cose importanti che si fanno con i riferimenti. =head1 Crediti Autore: Mark Jason Dominus, Plover Systems (C) Questo articolo E apparso in origine in I ( http://www.tpj.com/ ) volume 3, numero 2. Ristampato dietro permesso. Il titolo originale dell'articolo E I. =head2 Condizioni di Distribuzione Copyright 1998 The Perl Journal. Questa documentazione E libera, potete redistribuirla e/o modificarla negli stessi termini di Perl stesso. Indipendentemente dalla modalitE di distribuzione, tutti gli esempi di codice in questi file sono qui dichiarati di dominio pubblico. Vi E consentito (ed anzi siete incoraggiati a farlo) di usare questo codice nei vostri programmi per divertimento e profitto, come vi sembra meglio. Un semplice commento nel codice per dare credito sarebbe cortese ma non E richiesto. =head1 TRADUZIONE =head2 Versione La versione su cui si basa questa traduzione E ottenibile con: perl -MPOD2::IT -e print_pod perlreftut Per maggiori informazioni sul progetto di traduzione in italiano si veda L . =head2 Traduttore Traduzione a cura di larsen, con adeguamenti di Flavio Poletti. =head2 Revisore Revisione a cura di bepi. =cut