Tipi di dato e contesto in Perl

 

Concetto basilare in Perl, il contesto è quello nel quale una variabile o una sezione di codice sono valutati, e indica il tipo di valore che si vuole ottenere in una determinata situazione. Naturalmente il contesto sta ad indicare anche il tipo di valore che possiamo assegnare ad un particolare tipo di variabile. In questo documento parleremo dei tipi di dato ammissibili in Perl, nell’ottica del concetto di contesto che verrà finalizzato in seguito.

Dati scalari

Chiamati literals o literal data (dati letterali), sono dei valori: numeri o stringhe:

42      # intero
3.1415  # virgola mobile
-2      # intero negativo

2.31e4 = 23100      # notazione scientifica
2.31e-4 = 0.000231  # notazione scientifica

0213   # ottale   (213)8=(139)10
0x1fa  # esadecimale (1fa)16=(506)10

1_234_567      # equivalente a 1234567
7_654.321_789  # equivalente a 7654.321789

'Ciao'   # stringa tra apici singoli (single-quoted)
"Ciao"   # stringa tra apici doppi (double-quoted)

Espressioni

Un’espressione è una entità che viene valutata in un valore, ad esempio tramite operatori numerici (+ - * / ** %) oppure operatori su stringhe (.  x)

3 + 5
'ciao' . 'mondo'

undef

È il valore indefinito e non è propriamente un tipo di dato, ma indica l’assenza di un valore in una variabile. Può essere pensato, per comodità, come un tipo di dato con un solo possibile valore: NULL. Possiamo assegnare il valore indefinito ad uno scalare, per cui il valore indefinito può anche essere pensato come un caso speciale di valore scalare.

Liste

Una lista è una collezione ordinata e finita di valori scalari o di espressioni, separate da virgole. Viene costruita mediante l’operatore virgola “,” che concatena tra di loro degli scalari per produrre un valore di lista. Di norma, visto che la precedenza dell’operatore “,” è molto bassa, per disambiguarne il comportamento si racchiude la lista tra parentesi tonde.

(1 , 2 , 3 , 4)
('a' , 42 , "ciao")
(4+5 , $variabile , 1)

(1 , 2 , (3, 4) , 5); # è equivalente a (1, 2 , 3 , 4 , 5)

per ottenere un singolo elemento della lista basta mettere un indice alla fine della lista:

(1 , 2 , 3, 4)[2]; # dà il valore 3

Una volta definita, una lista non può crescere o diminuire come numero di elementi.

Quale costruttore di liste può essere usato l’operatore qw (detto word list, lista di parole), come ad esempio in

qw(uno due tre)

Variabili scalari

Chiamate semplicemente anche scalari, sono degli slot di memoria associati ad un nome, nei quali si può immagazzinare un singolo dato, che può essere:

  • un numero: $variabile=5;
  • una stringa: $variabile = 'Ciao';
  • undef
  • un riferimento

Le variabili scalari vengono sempre prefissate da un carattere $ (che potremmo pensare come la s di scalar).

Variabili aggregate (array)

Contengono una lista di scalari: sono liste a cui è data la capacità di crescere illimitatamente, diminuire, ed essere manipolate

@numeri=(1 , 2 , 'tre');
@varie=($costo , $peso , 3 + 4);
@copia=@originale;
@nuovo=(0 , @numeri , 4, 5 , 6);

che è lo stesso che scrivere

@nuovo=(0 , (1 , 2 , 'tre') , 4, 5 , 6);

che quindi diventa

@nuovo=(0 ,1 , 2 , 'tre', 4, 5 , 6);

Gli array sono ordinati, nel senso che esiste un indice numerico che si riferisce alla posizione, che ci permette di accedere al singolo elemento della lista:

@numeri=(9 , 10 , 11 , 12);
$secondo= $numeri[1]; # assegna 10 alla variabile $secondo

dunque ci si riferisce ad un elemento di un array facendolo precedere dal simbolo $ e non dal @, infatti gli array sono liste di scalari, dunque un singolo elemento è di fatto un singolo scalare, che è dunque denotato con il simbolo $ .

L’assegnamento di un array è equivalente all’assegnamento tra liste, infatti quello che avviene in

@numeri=(9 , 10 , 11);

è questo:

($numeri[0] , $numeri[1] , $numeri[2])=(9 , 10 , 11);

L’assegnamento appena visto non avviene solo per elementi di array (che sono scalari) ma per ogni lista di scalari:

($netto, $tara) = (42, 12);

($netto, $tara) = ($tara, $netto); # scambia fra loro $netto e $tara

quest’ultima riga è possibile perché; la parte destra dell’assegnamento viene valutata per prima, dunque viene prima creata la lista di destra che viene poi assegnata alla lista di variabili alla sinistra.

Lo “slice” è una lista che viene estratta da un array mediante:

  • un range di indici grazie all’operatore range “..” che nel caso seguente restituisce la lista (2, 3, 4)

($tre, $quattro, $cinque)=@array[2..4];

  • oppure attraverso un insieme di indici:

($primo, $secondo, $penultimo, $ultimo)=@array[0, 1, -2, -1];

  • oppure attraverso un insieme di range di indici:

@array=(1,2,3,4,5);
print "@array\n";

@array=@array[0..2, 0..2, 0..2];
print "@array\n";

Gli slice sono usati anche per modificare il valore di un array

  • non siamo costretti a cambiare un singolo elemento di un array alla volta, ma possiamo cambiarne più di uno alla volta:

@array[2..4] = @array[0..2];
@array[3..5 , 7 , -1] = qw(quarto quinto sesto ottavo ultimo);

  • se alla destra dell’assegnamento “offro” più elementi del necessario, questi vengono
    ignorati:

@array = (1, 2, 3, 4, 5, 6);
@array[0 .. 2] = (7, 8, 9, 10);

La funzione splice (perldoc -f splice) sostituisce parti di un array con liste e restituisce la lista di elementi sostituiti. Nel caso volessimo rimuovere uno o più elementi di un array, basta lasciare vuota la lista di sostituzione:

@array = qw( a b c d e f );
@elementi_rimossi = splice @array, 2, 3;

Variabili aggregate (hash)

Chiamate anche array associativi, sono una collezione di valori scalari (ne possono contenere un numero illimitato) e sono definiti tramite coppie di chiavi e valori. Non sono liste, ma le liste vengono spesso usate per definirli:

%hash = ( 'elemento_a' , 42, 'elemento_b', 12 );

è una lista di coppie “chiave-valore”, che è come dire

%hash = @valori; # @valori è array con un numero pari di elementi

i valori degli hash possono essere scalari ma le chiavi devono essere stringhe, per cui vanno racchiuse tra virgolette:

'elemento_a'

L’operatore => (“fat comma”, virgola grassa) è usato per agevolare l’assegnamento di valori agli hash in modo da chiarire, a livello di “occhiata” al codice che non si tratta di una semplice lista:

( elemento_a => 42, elemento_b => 12 )

In questo caso è subito chiaro che si tratta di coppie chiave-valore, inoltre il quoting delle stringhe non è più necessario visto che l’operatore => tratta l’operando alla sua sinistra come una stringa. L’assegnamento di un hash diventa dunque:

%hash = ( elemento_a => 42, elemento_b => 12 );

La “fat comma” tratta l’operando a sinistra come una stringa solo se ha lo stesso formato di un identificatore in particolare, in $ciao => 'a tutti', $ciao non viene racchiuso tra virgolette doppie automaticamente vista la presenza di $.

L’utilizzo di stringhe quali valori per gli indici non ci impedisce di usare variabili per memorizzare tali stringhe

($topo, $gatto) = ('mouse', 'cat' );
%hash = ($topo => 'jerry' , $gatto => 'tom');

Per utilizzare chiavi non banali, si possono usare il single e double quoting:

%hash = ('la media' => 42, "il $gatto" => 'tom');

Per l’assegnamento si può utilizzare anche l’operatore qw:

%hash = qw(
      gatto tom
      topo jerry
);

Bisogna tenere a mente che i valori immagazzinati da un hash sono scalari per cui per accedere ad un valore di un hash ci si riferirà alla variabile prefissandola con il simbolo $

print $hash{'gatto'};

Da notare che la chiave è una stringa e andrebbe quotata per buona regola, ma non è  strettamente necessario (è necessario se vi sono spazi): le parentesi comunicano già al Perl  che si tratta di hash, dunque di chiavi, dunque di stringhe (vale la stessa regola citata per la  (“fat comma”).

Possiamo anche estrarre valori multipli (dunque liste) utilizzando liste di chiavi:

@animali = ($topo, $gatto) = ('mouse', 'cat' );
@elementi = @hash{ @animali };

@elementi = @hash{ 'mouse' , 'cat' };

Davanti al nome di un hash si utilizza in questo caso @ perché; si sta richiedendo una lista di valori.

Si può cambiare un valore di un hash: se è gia presente la chiave, esso verrà sovrascritto $hash{'gatto'}='silvestro';

altrimenti verrà aggiunto a quelli esistenti

$hash{'canarino'}='titti';

Come per la lettura di valori, anche per l’inserimento si possono usare le liste, nel seguente modo:

@hash{@chiavi} = @valori;

ad esempio:

@hash{'gatto' , 'topo'} = ( 'silvestro' , 'speedy' );

che è equivalente a:

$hash{'gatto'} = 'silvestro';
$hash{'topo'} = 'speedy';

Si possono anche costruire hash tramite range:

@codici{'A' .. 'Z'} = 1 .. 26;

ed anche leggerne i valori:

@codici_parziali=@codici{'D' .. 'H'};

Per cancellare un valore, lasciando però la chiave, si utilizza la funzione undef (perldoc -f undef)

undef $hash{'cane'};

Per eliminare del tutto la coppia chiave-valore, si utilizza la funzione delete (perldoc -f delete)

delete $hash{'cane'};

Riferimenti (reference)

Quando si è parlato della variabile scalare, si è detto che è uno slot di memoria che può contenere dati, a cui è associato un nome. Tale slot di memoria viene individuato fisicamente attraverso un indirizzo, ed il Perl internamente mantiene una sua lista di  associazioni nome-indirizzo.

Il Perl dà la possibilità di assegnare ad una variabile l’indirizzo di memoria di un’altra variabile. Questo è detto assegnamento di un (hard) reference ad una variabile e si indica  in questo modo:

$variabile = 42;
$variabile_riferimento = \$variabile;

In questo caso $variabile_riferimento è una normale variabile Perl che contiene uno scalare, che noi sappiamo essere un indirizzo di memoria che contiene lo scalare ospitato dalla variabile $variabile. Tale indirizzo di memoria avrà una sua rappresentazione interna in Perl e sarà qualcosa tipo: SCALAR(0x8070df7)

Per utilizzare il riferimento al fine di accedere al valore della variabile a cui il riferimento “punta”, occorre deferenziare il riferimento. Il Perl si aspetta di trovare dopo il simbolo $ , un qualcosa che gli permetta di avere subito un indirizzo. Infatti trovando il nome di una variabile, tramite l’accoppiata nome-indirizzo di memoria, il Perl ha subito a disposizione tale indirizzo. Dunque se si fa seguire al $ qualcosa che contiene un indirizzo di memoria allora siamo a posto. Ma dato che nel nostro esempio $variabile_riferimento contiene l’indirizzo di memoria di $variabile, allora facendo:

$$variabile_riferimento

noi possiamo accedere direttamente al valore di $variabile.

$pippo = 42;
$pluto = \$pippo;

print $pluto;
print $$pluto;

In $$pluto, Perl trova un primo segno $ e va alla ricerca di un’informazione che porti ad un indirizzo. Trova $pluto e in $pluto c’è l’indirizzo valido di $pippo. Dunque userà questo indirizzo per prelevarvi il valore contenuto: 42.

Un riferimento è sempre un valore scalare (contiene, tra le altre cose, la rappresentazione interna che il Perl ha di un indirizzo di memoria che non è altro che un numero) ma naturalmente può fare riferimento anche all’indirizzo di memoria di un array o di un hash:

@array = (1 , 2 , 3);
$array_ref = \@array;
@secondo_array = @$array_ref;

Anche in questo caso, dopo il simbolo @, il Perl si aspetta di trovare una informazione che lo porti ad un indirizzo di memoria valido. Per i singoli elementi di un array (o di un hash) che sono scalari, vale quanto visto prima:

$elemento2 = $$array_ref[1];

Cosa succede se si tenta di deferenziare un valore che non è un hard reference? Tale valore viene trattato come riferimento simbolico (symbolic reference), cioè il riferimento è interpretato come una stringa che rappresenta il nome di una variabile (globale).

$prova = 1;
$ref='prova';

print $$ref;

Filehandle

Un filehandle è una sorta di linea di comunicazione tra il programma ed un canale (file, device, socket, pipe) di input (e.g. tastiera) o di output (e.g. video). Se si pensa alla variabile come associazione nome-indirizzo di memoria, un filehandle può essere pensato come una associazione nome-canale di comunicazione. Il filehandle nasconde dunque tutta una complicata struttura di informazioni che implementa la linea di comunicazione tra programma e device. Quando si vorrà scrivere o leggere su un canale, si scriverà sul o si leggerà dal filehandle. Le variabili di tipo filehandle non hanno alcun simbolo davanti al nome e, per convenzione, i nomi di filehandle sono scritti con tutte le lettere maiuscole. Quando un programma Perl viene eseguito esso ha già 3 filehandle predefiniti “aperti” alla comunicazione: STDIN (standard input), STDOUT (standard output) e STDERR (standard error), dove STDIN è associato alla tastiera e STDOUT e STDERR al video. Dunque quando scriviamo

print "ciao";

è come se noi stessimo scrivendo

print STDOUT "ciao";

Typeglob

Il typeglob è un tipo di dato composto che contiene una istanza di ogni altro tipo di dato. È un amalgama (in gergo Perl: un glob) di tutti gli altri tipi di dati dai quali condivide il nome: è una sorta di super-riferimento il cui valore non è un singolo indirizzo ma un qualcosa che ha 6 slot, 6 posizioni che contengono 6 differenti riferimenti:

  1. scalar
  2. array
  3. hash
  4. code
  5. handle
  6. format

Viene utilizzato per scopi avanzati (come il passaggio di filehandle alle subroutine, nel qual caso però si è soliti usare librerie apposite oppure i filehandle lessicali, $fh).

Una variabile glob viene preceduta dal simbolo * e ad essa possono essere assegnati i riferimenti dei vari tipi di dato:

*variabile_glob = \$scalare;
*variabile_glob = \@array;
*variabile_glob = \%hash;

Per accedere ai valori della variabile glob (che sono riferimenti), nelle versioni più recenti di Perl si può fare così:

$ref_scalare = *glob{SCALAR};
$ref_array = *glob{ARRAY};
$ref_hash = *glob{HASH};
$ref_sub = *glob{CODE};
$ref_filehandle = *glob{IO};

Contesto

Dopo questa panoramica sui tipi di dato del Perl, passiamo ad elencare i tipi di contesto:

  • contesto scalare

si ha quando viene richiesto (l’utilizzo di) un singolo valore

$variabile = 'Un solo valore';

Ma cosa succede se assegno una lista ad uno scalare? Il contesto “vincente” è quello della variabile dunque in questo caso il contesto è scalare. Quindi con:

$variabile = qw(una lista di stringhe);

forzo la valutazione di una lista in un contesto scalare. Dato che in uno scalare posso memorizzare un solo valore, otterrò che in $variabile ci sarà l’ultimo valore della lista: ‘stringhe’.

Un caso particolare è l’assegnamento di un array ad uno scalare:

$variabile = @array;

in questo caso (contesto scalare), $variabile conterrà il numero di elementi dell’array.

Nel caso in cui sia un hash ad essere assegnato ad uno scalare:

$variabile = %hash;

$variabile, se l’hash è vuoto, conterrà 0, altrimenti conterrà una stringa composta dal numero di bucket usati e dal numero di bucket allocati, separati da uno / (un bucket è lo slot di memoria nel quale si memorizza il valore associato ad una chiave)

  • contesto di lista

si ha quando viene richiesto (l’utilizzo di) una lista di 0 o più valori

@variabile = qw(una lista di stringhe);

Ma cosa succede se assegno uno scalare ad una variabile in contesto di lista? Il contesto “vincente” è quello della variabile e dunque il contesto è quello di lista. Dunque con:

@array = 'Un solo valore';

forzo la valutazione di uno scalare in un contesto di stringa. In questo caso lo scalare viene convertito in stringa e poi assegnato alla variabile @array. Ciò è equivalente a :

@array = ('Un solo valore');

Anche gli hash sono assegnati in un contesto di lista, per cui assegnare uno scalare ad un hash è come definire un hash che ha una sola chiave che fa coppia con un valore indefinito. Infatti lo scalare viene convertito in una lista che ha 1 solo elemento che dunque diventa l’unica chiave dell’hash (senza però alcun valore associato).

  • contesto booleano

esiste nel caso in cui un’espressione viene valutata per vedere se essa sia vera o falsa. Avviene dunque in contesti condizionali: alcuni operatori che in un contesto di lista si comportano in una certa maniara, se sottoposti a verifica condizionale (dunque in un contesto booleano) si comportano in un’altra.

  • contesto vuoto (void)

quando non viene richiesto alcun valore; avviene di rado, quando ad esempio si usa un valore ma senza usarlo o assegnarlo, come in:

"Ciao ciao";
miasub();

  • contesto interpolativo

avviene all’interno delle virgolette doppie (double quotes “) o con operatori che hanno comportamenti analoghi alle virgolette doppie (dunque che hanno a che fare con l’interpolazione di variabili) come ad esempio l’operatore qq (perlodc -f qq).

Citiamo infine due funzioni predefinite del Perl che hanno a che fare con il contesto.

  • wantarray()

è una funzione da utilizzare in una subroutine per capire in che contesto è stata chiamata tale subroutine.

Chiamate di funzione:

miasub();               # contesto vuoto

$a = miasub();          # contesto scalare
if (miasub()) {  }      # contesto scalare/booleano

@a = miasub();          # contesto di lista
print miasub();         # contesto di lista

sub miasub {

     #[CUT]

     if (wantarray()) {

           print "In contesto di lista\n";
           return @molti_elementi;

     } elsif (defined wantarray()) {

           print "In contesto scalare\n";
           return $un_elemento;

     } else {

           print "In contesto vuoto\n";
           return;  # nulla
     }
}

  • scalar

viene usata quando si vuole forzare la valutazione di un’espressione in contesto scalare, ovvero quando la valutazione della stessa in contesto di lista avrebbe un diverso comportamento (producendo dunque un diverso risultato), ad esempio:

print "La lunghezza dell'array \@ARRAY e`: ", scalar(@ARRAY), "\n";

Non esiste una funzione di lista corrispondente a scalar (ovvero che da singolo scalare lo promuova a lista) visto che ogni operazione che vuole una lista, fornisce già un contesto di lista ai suoi argomenti.

Facebooktwitterredditpinterestlinkedinmail