The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NOME

Il brevissimo tutorial di Mark a proposito dei riferimenti

Descrizione

Una delle più importanti nuove caratteristiche di Perl 5 era la capacità 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 è la chiave per gestire, con Perl, dati complessi e strutturati. Sfortunatamente, c'è un sacco di sintassi bizzarra da imparare, e la pagina principale del manuale può risultare difficile da seguire. Il manuale è piuttosto completo, e a volte la gente trova che questo sia un problema, poiché può essere difficile comunicare cosa è importante e cosa non lo è.

Fortunatamente, avete bisogno solo di un 10% di quanto viene detto nella pagina principale, per avere il 90% del beneficio. Questa pagina vi mostrerà quel 10%.

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.

Perché si dovrebbe volere una hash di liste? Prendiamo un semplice esempio: avete un file contenente nomi di città 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 città presenti in quel paese, in ordine alfabetico:

        Finlandia: Helsinki.
        Germania: Berlino, Francoforte.
        USA: Chicago, New York, Washington.

La maniera naturale per fare ciò è avere una hash le cui chiavi siano i nomi dei paesi. Associata ad ogni nome di paese c'è una lista con le città presenti in quel paese. Ogni volta che si legge una linea dell'input, la si spezza (split) in un paese e in una città, si consulta la lista di città già registrate come appartenenti ad un paese, e si appende la nuova città alla lista. Una volta finito con l'input, si itera sulla hash come al solito, ordinando ogni lista di città 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 città 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. È un lavoraccio, e soggetto ad errori. Ed è frustrante, poiché il Perl ha già le liste, del tutto adatte, che potrebbero risolvere il problema se soltanto poteste usarle.

La soluzione

Nel tempo in cui Perl 5 nasceva, eravamo già impantanati in questo design: i valori delle hash devono essere scalari. La soluzione sono i riferimenti.

Un riferimento è un valore scalare che si riferisce ad un intero array oppure ad un'intera hash (o a qualunque altra cosa). I nomi sono tipi di riferimento con cui probabilmente avete già familiarità. 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 ciò di cui c'è bisogno è 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. Però un singolo riferimento può referenziare un intero array, e i riferimenti sono scalari, quindi potete avere una hash di riferimenti ad array, e sarà utile quanto una hash di array.

Torneremo al problema paesi-città più tardi, dopo che avremo visto un po' di sintassi per gestire i riferimenti.

Sintassi

Ci sono solo due modi per costruire un riferimento, e solo due modi per usarne uno una volta che lo avete.

Costruire riferimenti

Primo modo

Se mettete un \ 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 è memorizzato in una variabile come $aref o $href, potete copiarlo o memorizzarlo così 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 può capitare che vogliate fare un array o una hash senza nome. È un caso analogo all'usare la stringa "\n" o il numero 80 senza che esso sia memorizzato prima in una variabile.

Secondo modo

[ ELEMENTI ] crea un nuovo array anonimo, e restituisce un riferimento ad esso. { 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 è un'abbreviazione delle altre due, eccetto per il fatto che non crea la variabile @array, che è superflua.

Se scrivete semplicemente [] ottenete un nuovo array anonimo, vuoto. Se scrivete semplicemente {} ottenete una nuova hash anonima, vuota.

Usare i riferimenti

Che cosa si può fare con un riferimento una volta che ne avete uno? È 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:

Primo metodo

Se $aref contiene un riferimento ad un array, allora si può mettere {$aref} ovunque avreste normalmente messo il nome di un array. Ad esempio, @{$aref} anziché @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 @a, mentre la versione a destra opera sull'array che è referenziato da $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 è 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 Primo metodo vi dice come farlo. È sufficiente che scriviate il codice Perl che avreste scritto per fare la stessa identica cosa con un normale array o hash, e poi sostituire {$reference} al nome dell'array o dell'hash. "Come faccio un ciclo su un array quando tutto quel che ho è un riferimento?" Beh, per fare un ciclo su un array regolare, dovreste scrivere

   for my $elemento (@array) {
      ...
   }

quindi rimpiazzate il nome dell'array, array, con il riferimento fra parentesi graffe:

   for my $elemento (@{$riferimento_ad_array}) {
      ...
   }

"Come faccio a stampare il contenuto di una hash quando tutto ciò che ho è 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";
   }

Secondo metodo

Il Primo metodo di utilizzo è tutto ciò di cui avete veramente bisogno, perché vi dice come fare qualunque cosa di cui possiate mai avere bisogno. Ma l'operazione più comune su un array o su una hash è l'estrazione di un singolo elemento, e la sintassi del Primo metodo è involuta. Per questo motivo esistono delle abbreviazioni.

${$aref}[3] è troppo difficile da leggere; al suo posto potete usare $aref->[3].

${$href}{red} è troppo difficile da leggere; al suo posto potete usare $href->{red}.

Se $aref è un riferimento ad array, allora $aref->[3] è il quarto elemento dell'array. Non confondetelo con $aref[3], che è il quarto elemento di un array del tutto diverso, ingannevolmente chiamato @aref. $aref e @aref non hanno alcun rapporto tra loro, così come non lo hanno $item e @item.

Analogamente, $href->{'red'} è parte della hash referenziata dalla variabile scalare $href, forse addirittura di una hash anonima. $href{'red'} è parte della hash dall'ingannevole nome $href. È facile dimenticarsi il <->>, e se lo fate, otterrete bizzarri risultati quando il vostro programma prenderà elementi da array ed hash totalmente inaspettati, che non sono quelli che volevate usare.

Un esempio

Vediamo un esempio di come questo sia utile.

Per prima cosa, ricordate che [1, 2, 3] crea un array anonimo contenente (1, 2, 3) e restituisce una reference a tale array.

Ora pensate a

   @a = ( [1, 2, 3],
          [4, 5, 6],
          [7, 8, 9]
        );

@a è un array di tre elementi, ognuno dei quali è un riferimento ad un altro array.

$a[1] è uno di questi riferimenti. Referenzia un array, l'array contenente (4, 5, 6), e poiché è un riferimento ad un array, la dice che possiamo scrivere $a[1]->[2] per ottenere il terzo elemento da tale array. $a[1]->[2] è 6. Allo stesso modo, $a[0]->[1] è 2. Quello che abbiamo è come un array bidimensionale; si può scrivere $a[RIGA]->[COLONNA] per avere (o modificare) l'elemento nella riga e colonna desiderate.

La notazione pare ancora poco maneggevole, quindi c'è ancora un'altra abbreviazione.

La regola della freccia

Tra due indici, la freccia è opzionale.

Al posto di $a[1]->[2], si può scrivere $a[1][2]; vuol dire la stessa cosa. Anziché $a[0]->[1], si può scrivere $a[0][1]; è la stessa cosa.

Ora sembrano davvero array bidimensionali!

Potete rendervi conto del perché le freccie sono importanti. Senza di esse, dovremmo scrivere ${$a[1]}[2] anziché $a[1][2]. Per array tridimensionali, la freccia consente di scrivere $x[2][3][5] anziché ${${$x[2]}[3]}[5]: illeggibile.

La soluzione

Ed ecco la risposta al problema che avevo posto prima, riformattare un file contenente nomi di città 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 è 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, %tabella, le cui chiavi sono i nomi delle nazioni, ed i cui valori sono riferimenti ad array contenenti i nomi delle città. La struttura dati avrà più o meno questo aspetto:

           %tabella
        +-----------+---+
        |           |   |   +-------------+---------+
        | Germania  | *---->| Francoforte | Berlino |
        |           |   |   +-------------+---------+
        +-----------+---+
        |           |   |   +----------+
        | Finlandia | *---->| Helsinki |
        |           |   |   +----------+
        +-----------+---+
        |           |   |   +---------+------------+----------+
        |    USA    | *---->| Chicago | Washington | New York |
        |           |   |   +---------+------------+----------+
        +-----------+---+

Per cominciare, ci concentreremo sull'uscita. Supponendo di avere già 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 }

%tabella è 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 è alla riga 10. $tabella{$nazione} cerca la chiave $nazione nella hash e trova il valore corrispondente, che è un riferimento ad un array di città in quella determinata nazione. Il Primo metodo ci dice che possiamo arrivare all'array con l'espressione @{$tabella{$nazione}}. La riga 10 è del tutto analoga a

   @le_citta = @array;

eccetto che il nome array è stato rimpiazzato dal riferimento (fra parentesi graffe) {$table{$country}}. Il sigillo @ dice a Perl di prendere l'intero array. Una volta presa la lista delle città, la ordiniamo e la stampiamo come di consueto.

Le righe 2--7 hanno la responsabilità 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 città e della nazione in cui si trova. La riga 5 verifica se il nome della nazione è già presente come chiave nella hash. Se non è così, il programma utilizza la notazione [] (in linea con il Secondo modo di generare un riferimento ad array) per costruire un nuovo array anonimo (e vuoto) per contenere i nomi delle città, e installa un riferimento a questo array nella hash, sotto la chiave corrispondente alla nazione.

La riga 6 isntalla il nome della città nell'array più appropriato. $tabella{$nazione} ora contiene un riferimento all'array delle città viste fino ad ora nella determinata nazione. La riga 6 è esattamente come

   push @array, $citta;

ad eccezione del fatto che il nome array è stato sostituito dal riferimento fra parentesi graffe {$tabella{$nazione}}. La chiamata alla funzione push aggiunge il nome della città in fondo all'array "puntato" dal riferimento.

Ho saltato un aspetto sottile. La riga 5 non è 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 già esiste un elemento in %tabella per la $nazione corrente non cambia niente. La riga 6 individuerà il valore in $tabella{$nazione}, che è un riferimento ad un array, ed aggiungerà il nome della città in coda all'array stesso. Ma cosa fa quando $nazione contiene il nome di una chiave, poniamo Grecia, che ancora non esiste in %tabella?

Stiamo parlando di Perl, per cui fa la cosa giusta. In particolare, Perl si accorge che volete inserire Atene in un array che non esiste, e si rende utile creando un nuovo array anonimo e vuoto al posto vostro, installandolo all'interno di %tabella; successivamente, effettua l'inserimento di Atene. Questo comportamento è chiamato `autovivificazione'--portare le cose in vita automaticamente. Perl nota che la chiave non è 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 città.

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 perlref, che parla del 100% dei dettagli.

Alcuni punti importanti di perlref:

  • Si possono costruire riferimenti a qualunque cosa, compresi scalari, funzioni e altri riferimenti.

  • Nel Primo metodo di costruzione, si possono omettere le parentesi graffe quando ciò che contengono è una variabile scalare atomica come $aref. Ad esempio, @$aref è uguale a @{$aref}, e $$aref[1] è lo stesso che ${$aref}[1]. Se state cominciando, magari potreste adottare l'abitudine di usare sempre le parentesi graffe.

  • Quanto segue non effettua una copia dell'array sottostante:

       $rif_array2 = $rif_array1;

    Tutto ciò che ottenete sono due riferimenti allo stesso array. Se modificate $rif_array1->[23] vedrete che anche $rif_array2->[23] è cambiato.

    Per copiare l'array, usate

       $rif_array2 = [@{$rif_array1}];

    In questo modo viene utilizzata la notazione [...] per creare un nuovo array anonimo, un riferimento al quale viene assegnato a $rif_array2. Il nuovo array è inizializzato con il contenuto dell'array riferito da rif_array1.

    Similmente, per copiare una hash anonima potete utilizzare

       $rif_hash2 = {${$rif_hash1}};
  • Per vedere se una variabile contiene un riferimento, si può usare la funzione `ref'. Restituisce un valore vero se l'argomento è un riferimento. In effetti, è ancora meglio: restituisce la stringa HASH per i riferimenti ad hash, e la stringa ARRAY per i riferimenti ad array.

  • 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 è il fatto che si può usare eq per vedere se due riferimenti referenziano la stessa cosa (ma è meglio usare ==, che è più veloce.)

  • Potete usare una stringa come se fosse un riferimento. Se usate la stringa "foo" come un riferimento ad array, è trattato come un riferimento all'array @foo. È chiamato soft reference o riferimento simbolico. La dichiarazione use strict 'refs'; [o anche use strict; e basta, NdT] disabilita questa caratteristica, che può causare ogni sorta di guaio se la usate per sbaglio.

Forse preferirete proseguire con perllol, anziché perlref; parla, in dettaglio, di liste di liste e di array multidimensionali. Poi, potreste passare a perldsc; è il "Ricettario delle Strutture Dati", che mostra le ricette per stampare array di hash, hash di array e altri tipi di dato.

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.

Crediti

Autore: Mark Jason Dominus, Plover Systems (mjd-perl-ref+@plover.com)

Questo articolo è apparso in origine in The Perl Journal ( http://www.tpj.com/ ) volume 3, numero 2. Ristampato dietro permesso.

Il titolo originale dell'articolo è Comprendere i Riferimento Oggi.

Condizioni di Distribuzione

Copyright 1998 The Perl Journal.

Questa documentazione è libera, potete redistribuirla e/o modificarla negli stessi termini di Perl stesso.

Indipendentemente dalla modalità di distribuzione, tutti gli esempi di codice in questi file sono qui dichiarati di dominio pubblico. Vi è 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 è richiesto.

TRADUZIONE

Versione

La versione su cui si basa questa traduzione è ottenibile con:

   perl -MPOD2::IT -e print_pod perlreftut

Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ .

Traduttore

Traduzione a cura di larsen, con adeguamenti di Flavio Poletti.

Revisore

Revisione a cura di bepi.