Perl Oltre la Sintassi

Con il Perl scolastico potete arrivare solo fino a un certo livello. Una vera scioltezza nel linguaggio vi permette di usare i suoi pattern e idiomi naturali. I programmatori esperti comprendono come interagiscono e si combinano le funzionalità di Perl.

Preparatevi per una seconda curva di apprendimento di Perl: il modo di pensare "alla Perl". Il risultato sarà del codice conciso, potente e "alla Perl".

Idiomi

Ogni linguaggio—di programmazione o naturale—ha dei pattern comuni di espressione, o idiomi. La Terra gira, ma noi parliamo della nascita e tramonto del Sole. Tutti noi ci vantiamo delle trovate intelligenti e ci vergognamo di quelle stupide quando buttiamo fuori il nostro codice.

Gli idiomi Perl non sono né funzionalità del linguaggio né tecniche di progettazione. Sono piuttosto manierismi e meccanismi che, presi tutti insieme, danno al vostro codice un accento "alla Perl". Non è necessario che li usiate, ma sappiate che sono dei punti di forza di Perl.

L'Oggetto $self

Il sistema a oggetti di Perl 5 (Moose) tratta l'invocante di un metodo come un semplice parametro. Indipendentemente dal fatto che invochiate un metodo di classe o di istanza, il primo elemento di @_ è sempre l'invocante del metodo. La maggior parte del codice Perl 5 usa per convenzione $class come nome dell'invocante di un metodo di classe e $self come nome dell'oggetto invocante. Questa convenzione è talmente consolidata che alcune utili estensioni come MooseX::Method::Signatures assumono che usiate $self come nome degli oggetti invocanti.

Parametri per Nome

L'elaborazione di liste è una componente fondamentale della valutazione di espressioni in Perl. La possibilità che hanno i programmatori Perl di concatenare espressioni che valutano a liste di lunghezza variabile fornisce innumerevoli opportunità di manipolare efficacemente i dati.

Anche se la semplicità del passaggio di argomenti in Perl (ogni cosa viene appiattita in @_) risulta talvolta persino eccessiva, assegnando da @_ in contesto lista potete spacchettare i parametri per nome come coppie. L'operatore fat comma (Dichiarazione di Hash) trasforma in modo ovvio una normale lista in una lista di coppie di argomenti:

    prepara_coppa_di_gelato(
        panna_montata => 1,
        praline       => 1,
        banana        => 0,
        gelato        => 'menta e cioccolato',
    );

La funzione chiamata può spacchettare questi parametri in un hash e trattare tale hash come un singolo argomento:

    sub prepara_coppa_di_gelato
    {
        my %arg   = @_;
        my $dessert = compra_gelato( $arg{gelato} );

        ...
    }

Hash oppure Riferimento ad Hash?

Perl Best Practices suggerisce di passare dei riferimenti ad hash anziché degli hash. Questo permette a Perl di effettuare la validazione del riferimento ad hash nel chiamante.

Questa tecnica funziona bene con import() (Importazione) e altri metodi; elaborate tutti i parametri che desiderate prima di inghiottire tutto ciò che resta in un hash:

    sub import
    {
        my ($class, %arg)  = @_;
        my $package_chiamante = caller();

        ...
    }

La Trasformata di Schwartz

La trasformata di Schwartz è un'elegante dimostrazione della pervasività della gestione di liste in Perl in quanto è un idioma preso agevolmente a prestito dalla famiglia di linguaggi Lisp.

Supponete di avere un hash che associa i nomi dei vostri colleghi con le loro estensioni telefoniche:

    my %estensioni =
    (
        '001' => 'Anna',
        '002' => 'Walter',
        '003' => 'Gerardo',
        '005' => 'Rudy',
        '007' => 'Brenda',
        '008' => 'Patrizio',
        '011' => 'Luca',
        '012' => 'Lidia',
        '017' => 'Cristina',
        '020' => 'Maurizio',
        '023' => 'Marco',
        '024' => 'Andrea',
        '052' => 'Giorgia',
        '088' => 'Nicola',
    );

Regole di Quotatura delle Chiavi di Hash

La quotatura delle chiavi di hash con l'operatore fat comma funziona soltanto su entità interpretabili come bareword; le chiavi di questo esempio sono invece numeri—in particolare numeri ottali, poiché iniziano con lo zero. Quasi tutti cascano in questo tranello.

Per ordinare questa lista per nome in ordine alfabetico, dovete ordinare l'hash in base ai suoi valori anziché in base alle sue chiavi. Ordinare i valori è facile:

    my @nomi_ordinati = sort values %estensioni;

... ma dovete fare un ulteriore passo per preservare l'associazione tra nomi ed estensioni, e qui entra in gioco la trasformata di Schwartz. Per prima cosa, convertite l'hash in una lista di strutture dati facili da ordinare—in questo caso, array anonimi di due elementi:

    my @coppie = map  { [ $_, $estensioni{$_} ] }
                 keys %estensioni;

sort riceve tale lista di array anonimi e confronta i loro secondi elementi (i nomi) come stringhe:

    my @coppie_ordinate = sort { $a->[1] cmp $b->[1] }
                          @coppie;

Il blocco fornito a sort riceve i suoi argomenti in due variabili di package (Scope) $a e $b Vedete perldoc -f sort per una esaustiva discussione delle implicazioni di questa scelta degli scope.. Il blocco di sort prende due argomenti per volta; il primo diventa il contenuto di $a e il secondo il contenuto di $b. Se $a deve precedere $b nel risultato, il blocco restituisce -1. Se i due valori sono equivalenti per quanto riguarda l'ordinamento, il blocco restituisce 0. Infine, se $a deve seguire $b nel risultato, il blocco restituisce 1. Qualunque altro valore di ritorno è considerato un errore.

Conosci i Tuoi Dati

Invertire un hash sul posto funziona se non avete colleghi con lo stesso nome. I dati del nostro esempio non presentano problemi, ma il vostro codice deve difendervi da questa eventualità.

L'operatore cmp effettua confronti tra stringhe mentre l'operatore <=> effettua confronti numerici.

Una seconda operazione map su @coppie_ordinate converte la struttura dati in una forma più usabile:

    my @est_formattate = map { "$_->[1], est. $_->[0]" }
                         @coppie_ordinate;

... e finalmente potete stampare il risultato:

    say for @est_formattate;

La trasformata di Schwartz sfrutta la pervasività della gestione di liste in Perl per evitare l'uso delle variabili temporanee. La combinazione risultante è:

    say for
        map  { " $_->[1], est. $_->[0]"          }
        sort {   $a->[1] cmp   $b->[1]           }
        map  { [ $_      =>    $estensioni{$_} ] }
            keys %estensioni;

Leggete l'espressione da destra verso sinistra, nell'ordine di valuazione. Per ciascuna chiave dell'hash delle estensioni, viene creato un array anonimo di due elementi contenente la chiave e il corrispondente valore nell'hash. Tale lista di array anonimi viene ordinata in base ai secondi elementi, ovvero ai valori dell'hash. Infine per ciascuno di questi array ordinati viene generata una stringa di output nel formato opportuno.

La cascata map-sort-map della trasformata di Schwartz converte una struttura dati in una forma più facile da ordinare e quindi in una terza forma.

Mentre l'esempio di ordinamento descritto è semplice, considerate il caso in cui dovete calcolare hash crittografici per grandi file. La trasformata di Schwartz è particolarmente vantaggiosa poiché fa il caching dei risultati delle computazioni costose, eseguendole una sola volta nel map più a destra.

Slurp Semplificato di File

local è fondamentale per gestire le variabili globali predefinite di Perl 5. Dovete aver compreso bene gli scope (Scope) per usare efficacemente local—ma in tal caso potete usare degli scope brevi e leggeri in modi interessanti. Per esempio, per fare lo slurp di file in uno scalare in un'unica espressione:

    my $file = do { local $/ = <$fh> };

    # oppure
    my $file = do { local $/; <$fh> };

    # oppure
    my $file; { local $/; $file = <$fh> };

$/ è il separatore dei record di input. Quando viene localizzato, il suo valore viene impostato a undef fino al suo assegnamento. Tale localizzazione ha luogo prima dell'assegnamento e, dato che il valore del separatore non è definito, Perl non ha problemi a leggere l'intero contenuto del filehandle in una volta e ad assegnare il suo valore a $/. Poiché il valore di un blocco do è quello dell'ultima espressione valutata al suo interno, in questo caso il blocco valuta al valore dell'assegnamento, ovvero il contenuto del file. Sebbene al termine del blocco il valore di $/ venga immediatamente ripristinato al suo stato precedente, il valore finale di $file sarà il contenuto del file.

Nel secondo esempio il blocco non contiene assegnamenti e restituisce semplicemente la singola linea letta dal filehandle.

Il terzo esempio evita una seconda copia della stringa che rappresenta il contenuto del file; non è elegante come gli altri, ma usa una quantità minore di memoria.

File::Slurp

Questo utile esempio è sicuramente esasperante per le persone che non comprendono a fondo sia local che gli scope. Il modulo CPAN File::Slurp è una buona alternativa e spesso è anche più veloce.

Gestire il Main

Perl non ha una sintassi speciale per la creazione delle chiusure (Chiusure); può quindi succedere che facciate involontariamente la chiusura di una variabile lessicale. Molti programmi impostano svariate variabili lessicali nello scope del file prima di trasferire l'elaborazione ad altre funzioni. Potreste essere tentati di usare queste variabili direttamente, anziché passare e ricevere i loro valori dalle funzioni, specialmente quando il programma cresce di dimensioni. Sfortunatamente, in questo modo i programmi diventano dipendenti da sottigliezze riguardanti ciò che succede durante il processo di compilazione di Perl 5; una variabile che credevate di inizializzare subito ad uno specifico valore potrebbe essere inizializzata in un momento parecchio successivo.

Per evitare che questo accada, racchiudete il codice principale del vostro programma in una semplice funzione main(). Incapsulate le vostre variabili negli scope appropriati. Quindi, aggiungete una singola linea all'inizio del vostro programma, dopo aver usato tutti i moduli e le direttive di cui avete bisogno:

    #!/usr/bin/perl

    use Modern::Perl;

    ...

    exit main( @ARGS );

Chiamare main() prima di qualunque altra cosa nel programma vi impone di essere espliciti riguardo all'inizializzazione e all'ordine di compilazione. Chiamare exit con il valore restituito da main() previene l'esecuzione dell'eventuale codice successivo, ma fate attenzione a restituire 0 dal main() quando la sua esecuzione ha successo.

Esecuzione Controllata

La vera differenza tra un programma e un modulo è nell'uso che intendete farne. Gli utenti invocano i programmi direttamente, mentre i programmi caricano i moduli dopo l'inizio dell'esecuzione. Tuttavia, un modulo è fatto di codice Perl allo stesso modo di un programma. Rendere eseguibile un modulo è facile, così come fare in modo che un programma si comporti come un modulo (cosa utile per testare parti di un programma esistente senza creare formalmente un modulo). Tutto ciò che dovete fare è scoprire come Perl ha iniziato a eseguire un pezzo di codice.

L'unico argomento, peraltro opzionale, di caller è il numero di contesti di chiamata (Ricorsione) sui quali riportare informazioni. caller(0) riporta informazioni sul contesto di chiamata corrente. Per permettere a un modulo di essere eseguito correttamente sia come programma sia come modulo, mettete tutto il codice eseguibile all'interno di funzioni, aggiungete una funzione main() e scrivete la seguente linea all'inizio del modulo:

    main() unless caller(0);

Se non c'è alcun chiamante per il modulo, significa che qualcuno lo ha invocato direttamente come programma (con perl path/del/Modulo.pm invece di use Modulo;).

Migliorare l'Ispezione del Chiamante

L'ottavo elemento della lista restituita da caller in contesto lista è un valore vero se il contesto di chiamata rappresenta use o require, altrimenti è undef. Nonostante sia molto accurato, poche persone lo usano.

Validazione Postfissa dei Parametri

CPAN contiene svariati moduli che vi aiutano a verificare i parametri delle vostre funzioni; Params::Validate e MooseX::Params::Validate sono due opzioni valide. Una validazione basilare è facile anche senza tali moduli.

Supponete che la vostra funzione accetti esattamente due argomenti. Potreste scrivere:

    use Carp 'croak';

    sub sposa_scimmie
    {
        if (@_ != 2)
        {
            croak 'Il matrimonio richiede due scimmie!';
        }
        ...
    }

... ma, da un punto di vista linguistico, le conseguenze sono più importanti del controllo e meritano di essere all'inizio dell'espressione:

    croak 'Il matrimonio richiede due scimmie!' if @_ != 2;

... che potrebbe essere scritto in modo ancora più chiaro come:

    croak 'Il matrimonio richiede due scimmie!'
        unless @_ == 2;

Queste tecniche di terminazione prematura—specialmente quella con i condizionali postfissi—possono semplificare il resto del vostro codice. Ognuna di tali asserzioni è come una singola riga in una tabella di verità.

Regex En Passant

Molti degli idiomi di Perl 5 si basano sul fatto che le espressioni hanno dei valori:

    say my $num_est = my $estensione = 42;

Pur essendo chiaramente poco elegante, questo codice illustra come usare il valore di un'espressione in un'altra espressione. Non è un'idea nuova; avrete quasi sicuramente già usato il valore di ritorno di una funzione in una lista o come argomento di un'altra funzione. Forse però non avete pensato a tutte le sue implicazioni.

Supponete di voler estrarre un nome di battesimo da una combinazione di nome e cognome con un'espressione regolare precompilata in $nome_battesimo_regex:

    my ($nome_di_battesimo) = $nome =~ /($nome_battesimo_regex)/;

In contesto lista, un match riuscito di una regex restituisce una lista di tutte le catture (Cattura e Perl assegna la prima di esse a $nome_di_battesimo.

Per modificare il nome, ad esempio per rimuovere tutti i caratteri non di parola per creare un nome utente da usare per le credenziali di un sistema, potete scrivere:

    (my $nome_normalizzato = $nome) =~ tr/A-Za-z//dc;

/r in Perl 5.14

In Perl 5.14 è stato aggiunto il modificatore non distruttivo /r delle sostituzioni, quindi potete scrivere anche my $nome_normalizzato = $nome =~ tr/A-Za-z//dcr;.

Per prima cosa, assegnate il valore di $nome a $nome_normalizzato, dato che le parentesi modificano la precedenza in modo che l'assegnamento venga eseguito per primo. L'espressione di assegnamento valuta alla variabile $nome_normalizzato, quindi tale variabile diventa il primo operando dell'operatore di traslitterazione.

Questa tecnica è applicabile ad altri operatori di modifica sul posto:

    my $eta = 14;
    (my $nuova_eta = $eta)++;

    say "Il prossimo anno avro` $nuova_eta anni";

Coercizioni Unarie

Il sistema di tipi di Perl 5 fa quasi sempre la cosa giusta quando usate gli operatori corretti. Se usate l'operatore di concatenazione di stringhe, Perl tratterà entrambi gli operando come stringhe. Se usate l'operatore di addizione, Perl tratterà entrambi gli operandi come numeri.

Tuttavia, qualche volta dovete dare a Perl un suggerimento su ciò che intendete fare, usando una coercizione unaria per forzare un modo specifico di valutare un valore.

Per garantire che Perl tratti un valore come numerico, sommategli zero:

    my $valore_numerico = 0 + $valore;

Per garantire che Perl tratti un valore come booleano, fatene una doppia negazione:

    my $valore_booleano = !! $valore;

Per garantire che Perl tratti un valore come stringa, concatenatelo con la stringa vuota:

    my $valore_stringa = '' . $valore;

Sebbene sia estremamente improbabile che dobbiate usare queste coercizioni, è importante riconoscere questi idiomi quando li incontrate. Infatti, anche quando vi sembra una buona idea rimuovere un "inutile" + 0 da un'espressione, ciò potrebbe danneggiare il codice.

Variabili Globali

Perl 5 fornisce svariate variabili sovraglobali (che corrispondono alle variabili globali predefinite) il cui scope è realmente globale e non limitato a un package o a un file. Sfortunatamente, la loro accessibilità globale significa anche che ogni modifica diretta o indiretta dei loro valori può avere effetti su altre parti del programma; inoltre, i loro nomi sono alquanto criptici. Anche i programmatori Perl 5 esperti generalmente ne conoscono a memoria solo alcune, e ben poche persone le conoscono tutte. D'altra parte, solo un piccolo sottoinsieme di queste variabili può davvero esservi utile. La loro lista esaustiva è contenuta in perldoc perlvar.

Gestire le Variabili Sovraglobali

Le nuove versioni di Perl 5 continuano a convertire dei comportamenti globali in comportamenti lessicali; grazie a ciò, è possibile evitare di usare molte di queste variabili sovraglobali. Quando però non potete evitarle, usate local nello scope più ristretto possibile per limitare gli effetti delle vostre modifiche. In questo modo, anche se il vostro codice rimane esposto ai cambiamenti effettuati su tali variabili dal codice che chiamate, evitate almeno possibili sorprese da parte del codice esterno al vostro scope. Come illustrato dall'idioma dello slurp semplificato di file (Slurp Semplificato di File), local è spesso l'approccio giusto:

    my $file; { local $/; $file = <$fh> };

L'effetto della localizzazione di $/ dura soltanto fino alla fine del blocco. E ci sono poche possibilità che la lettura di linee da un filehandle provochi l'esecuzione di codice Perl Un filehandle tied (Tie) è una di queste possibilità. che modifica il valore di $/ all'interno del blocco do.

Non tutti i casi di utilizzo delle variabili sovraglobali sono così facili da trattare, ma spesso questo approccio funziona.

Talvolta potete aver bisogno di leggere il valore di una variabile sovraglobale, sperando che nel frattempo esso non sia stato modificato da altro codice. Catturare eccezioni con un blocco eval può dar luogo a problemi di concorrenza, dato che i metodi DESTROY() invocati sui lessicali dello scope da cui il programma è appena uscito potrebbero cancellare il valore di $@:

    local $@;

    eval { ... };

    if (my $eccezione = $@) { ... }

Copiate $@ immediatamente dopo aver catturato un'eccezione per preservare il suo contenuto. In alternativa, vedete Try::Tiny (Avvertenze sulle Eccezioni).

Nomi in Inglese

Il modulo English distribuito con Perl fornisce dei nomi lunghi (più facili da ricordare) per le variabili sovraglobali al posto di quelli composti di punteggiatura. Potete importarli in un namespace con:

    use English '-no_match_vars';

In questo modo potete usare i nomi lunghi documentati in perldoc perlvar all'interno dello scope della direttiva.

Tre variabili sovraglobali relative alle regex ($&, $` e $') impongono una penalità di efficienza che si applica globalmente a tutte le espressioni regolari di un programma. Se dimenticate di importare -no_match_vars, il vostro programma pagherà la penalità anche se non leggete esplicitamente da tali variabili.

I programmi in Perl moderno dovrebbero usare la variabile @- al posto di questo terribile terzetto di variabili.

Variabili Sovraglobali Utili

Molti programmi in Perl 5 moderno possono cavarsela usando solo un paio delle variabili sovraglobali. Probabilmente, incontrerete solo alcune di queste variabili nei programmi reali.

Alternative alle Variabili Sovraglobali

Le maggiori responsabilità di azioni a distanza ricadono sull'IO e sulle condizioni eccezionali. Usare Try::Tiny (Avvertenze sulle Eccezioni) vi aiuta a isolarvi dalla complicata semantica della gestione corretta delle eccezioni. localizzare e copiare il valore di $! può aiutarvi a evitare comportamenti strani quando Perl effettua chiamate implicite di sistema. Usare IO::File e i suoi metodi sui filehandle lessicali (Variabili Speciali di Gestione dei File) vi aiuta a prevenire modifiche globali indesiderate al comportamento dell'IO.