mercoledì 26 febbraio 2014

Scalare una lista in Perl (nel senso di applicare scalar!)

I contesti di invocazione di funzioni e operatori di Perl sono qualcosa di magico, e come ogni cosa magica non sempre funziona come ci si aspetterebbe. Qualche giorno fa una nota ha colpito la mia attenzione: l'uso dell'operatore scalar applicato ad una lista di liste.
Cerco quindi con questo articolo di chiarire un po' il concetto di "da lista a scalare" e "da scalare a lista".
Il passaggio da scalare a lista e' abbastanza facile: l'operatore ',' unito alle parentesi tonde permette di convertire una serie di scalari in una lista; e' inoltre possibile creare al volo liste di liste come ad esempio in:

my (($minute, $hour), ($day, $month, $year)) = (localtime())[1..5];

che, seppure privo di molto significato (poiche' si referenziano cinque scalari indipendenti) estrae due liste e assegna i singoli valori. La cosa e' equivalente a quanto segue:

my @time = (localtime())[1,2];
my @date = (localtime())[3,4,5];
my $minute = $time[ 0 ];
my $hour = $time[ 1 ];
my day = $date[ 0 ];
my month = $date[ 1 ];
my year = $date[ 2 ];

ma non equivalente a questa:

my ($minute, $hour, $day, $month, $year) = (localtime())[1..5];

che invece estrapola una lista sola assegnando i singoli valori come segue:

my @time_and_date = (localtime())[1..5];
my $minute = $time_and_date[ 0 ];
my $hour = $time_and_date[ 1 ];
my day = $time_and_date[ 2 ];
my month = $time_and_date[ 3 ];
my year = $time_and_date[ 4 ];

In un certo senso l'operatore ',' e' sovraccaricato: se valutato in contesto scalare rappresenta una continuazione del flusso di esecuzione (come in C), se in contesto di lista concatena gli elementi (scalari o no) in una lista.

L'operatore 'scalar' forza un contesto scalare dove c'e' un contesto di lista (ossia parentesi e virgola) annullando di fatto l'effetto delle parentesi e mutando il comportamento della virgola:

  • (,) viene valutato come virgola in contesto di lista
  • scalar (,) viene valutato come virgola in contesto scalare
  • scalar , viene valutato come virgola in contesto scalare

Ecco allora che il seguente semplice esempio:

#!/usr/bin/env perl -w

my (($minute, $hour), ($day, $month, $year)) = (localtime())[1..5];
$month++, $year += 1900;

print STDOUT "\nToday is $month / $day / $year at $hour:$minute\n";

print STDOUT "\n all variables as list: ", (($minute, $hour), ($day, $month, $year));
print STDOUT "\n all variables via scalar: ", scalar (($minute, $hour), ($day, $month, $year));


produce come output:

Today is 1 / 28 / 2014 at 13:48

all variables as list: 48132812014
all variables via scalar: 2014

Ossia la stampa della lista di liste produce, come ovvio, i valori concatenati, mentre la stampa 'scalar' della lista di lista produce la stampa del solo ultimo valore. In questo caso ogni operatore virgola viene trattato come continuazione del flusso di esecuzione. Da notare anche che la "lista di liste" è solo un fatto mentale: di fatto ogni espressione fra parentesi viene resa una lista piatta, e quindi non importa quante coppie di parentesi ci siano.
Da questo punto di vista l'operatore 'scalar' puo' essere pensato come un "anti-parentesi-di-lista". Da notare infatti che non conta quanto annidate siano le liste (ossia quante parentesi ci siano), l'espressione e' sempre valutata in contesto scalare come se fosse stata scritta senza parentesi.

La documentazione riporta in modo ben chiaro questo fatto:

Because "scalar" is a unary operator, if you accidentally use a parenthesized list for the EXPR, this behaves as a scalar comma expression, evaluating all but the last element in void context and returning the final element evaluated in scalar context. This is seldom what you want.

Nessun commento: