Funzioni

In Perl, una funzione è una unità di codice discreta ed incapsulata. Un programma è una collezione di queste piccole "scatole nere" le cui interazioni governano il controllo di flusso del programma stesso. Una funzione può avere o meno un nome. Può consumare informazioni in input oppure no. Può produrre informazione in output oppure no.

Le funzioni sono un meccanismo primario per l'astrazione, l'incapsulamento ed il riutilizzo del codice in Perl 5.

Dichiarazione di Funzioni

Usate il costrutto nativo sub per dichiarare una funzione:

    sub salutami  { ... }

Da questo momento salutami() può essere invocata dovunque all'interno del programma.

Non è necessario definire una funzione nello stesso punto in cui la dichiarate. Una dichiarazione anticipata dice al Perl di ricordarsi il nome della funzione anche se essa verrà definita dopo:

    sub saluta_dopo;

Invocazione di Funzioni

Usate delle parentesi postfisse (Posizione) e il nome di una funzione per invocare la funzione ed eventualmente passarle una lista di argomenti:

    salutami( 'Giacomo', 'Paolo' );
    salutami( 'Bianca' );
    salutami();

In questi esempi le parentesi non sono strettamente necessarie—anche se strict è attivata—ma rendono le cose più chiare sia a un lettore umano che al parser del Perl. In caso di dubbio, è meglio usarle.

Gli argomenti delle funzioni possono essere espressioni arbitrarie, incluse delle semplici variabili:

    salutami( $name );
    salutami( @authors );
    salutami( %editors );

... anche se la gestione di default dei parametri in Perl 5 riserva a volte delle sorprese ai principianti.

Parametri delle Funzioni

Una funzione riceve i suoi parametri in un singolo array, @_ (Le Variabili Array di Default). Perl appiattisce tutti i parametri in input in un'unica lista. La funzione può spacchettare i parametri in opportune variabili oppure operare direttamente su @_:

    sub saluta_uno
    {
        my ($nome) = @_;
        say "Ciao, $nome!";
    }

    sub saluta_tutti
    {
        say "Ciao, $_!" for @_;
    }

Anche se gran parte del codice usa shift o lo spacchettamento in una lista, @_ si comporta come un normale array del Perl, quindi potete riferirvi a singoli elementi per indice:

    sub saluta_uno_shift
    {
        my $nome = shift;
        say "Ciao, $nome!";
    }

    sub saluta_due_senza_shift
    {
        my ($supereroe, $aiutante) = @_;
        say "Ma sono proprio $supereroe e $aiutante... Benvenuti!";
    }

    sub saluta_uno_indice
    {
        my $nome = $_[0];
        say "Ciao, $nome!";

        # oppure, in modo meno chiaro
        say "Ciao, $_[0]!";
    }

... ma anche usare shift, unshift, push, pop, splice e le slice con @_.

Gli Essi Impliciti

Ricordate che le funzioni native che operano sugli array usano @_ come operando di default all'interno delle funzioni. Sfruttate questo idioma.

L'assegnamento di un parametro scalare da @_ richiede uno shift, un accesso indicizzato a @_ oppure la presenza di un contesto lista a sinistra dell'assegnamento stesso determinato dalle parentesi. Negli altri casi, Perl 5 non avrà problemi a valutare per voi @_ in contesto scalare e assegnare il numero di parametri passati:

    sub saluta_uno_con_bug
    {
        my $nome = @_;  # bug
        say "Ciao, $nome; oggi sembri un numero!"
    }

L'assegnamento di diversi parametri a una lista è spesso più chiaro delle corrispondenti linee di codice contenenti degli shift. Confrontate:

    sub calcola_valore
    {
        # shift multipli
        my $valore_sinistro = shift;
        my $operazione      = shift;
        my $valore_destro   = shift;
        ...
    }

... con:

    sub calcola_valore
    {
        my ($valore_sinistro, $operazione, $valore_destro) = @_;
        ...
    }

Occasionalmente, può essere necessario estrarre alcuni parametri da @_ e passare i restanti ad un'altra funzione:

    sub metodo_delegato
    {
        my $self = shift;
        say 'Chiamo metodo_delegato()'

        $self->delega->metodo_delegato( @_ );
    }

Usate shift quando la vostra funzione necessita di un solo parametro. Usate un assegnamento a lista quando dovete accedere a diversi parametri.

Vere Segnature di Funzione

Molte distribuzioni di CPAN estendono la gestione dei parametri di Perl 5 con sintassi e opzioni aggiuntive. signatures e Method::Signatures sono potenti. Method::Signatures::Simple è elementare, ma utile. MooseX::Method::Signatures funziona bene in coppia con Moose (Moose).

Appiattimento

L'appiattimento dei parametri in @_ avviene nel chiamante di una funzione. Passare un hash come argomento produce una lista di coppie chiave/valore:

    my %nome_e_tipo_cuccioli = (
        Lucky   => 'cane',
        Rodney  => 'cane',
        Tuxedo  => 'gatto',
        Petunia => 'gatto',
    );

    mostra_cuccioli( %nome_e_tipo_cuccioli );

    sub mostra_cuccioli
    {
        my %cuccioli = @_;
        while (my ($nome, $tipo) = each %cuccioli)
        {
            say "$nome è un $tipo";
        }
    }

Quando il Perl appiattisce %nome_e_tipo_cuccioli in una lista, l'ordine delle coppie chiave/valore dell'hash può variare, ma la lista conterrà sempre una chiave seguita immediatamente dal suo valore. L'assegnamento di hash all'interno di mostra_cuccioli() funziona essenzialmente allo stesso modo dell'assegnamento a %nome_e_tipo_cuccioli, anche se quest'ultimo è più esplicito.

L'appiattimento è spesso utile, ma occorre fare attenzione a non mescolare scalari e aggregati appiattiti nelle liste di parametri. Se scrivete una funzione mostra_cuccioli_per_tipo(), dove un parametro è il tipo di cuccioli da mostrare, passate tale tipo come primo parametro (o usate pop per rimuoverlo dalla fine di @_):

    sub mostra_cuccioli_per_tipo
    {
        my ($tipo, %cuccioli) = @_;

        while (my ($nome, $specie) = each %cuccioli)
        {
            next unless $specie eq $tipo;
            say "$nome è un $specie";
        }
    }

    my %nome_e_tipo_cuccioli = (
        Lucky   => 'cane',
        Rodney  => 'cane',
        Tuxedo  => 'gatto',
        Petunia => 'gatto',
    );

    mostra_cuccioli_per_tipo( 'cane',  %nome_e_tipo_cuccioli );
    mostra_cuccioli_per_tipo( 'gatto', %nome_e_tipo_cuccioli );
    mostra_cuccioli_per_tipo( 'alce',  %nome_e_tipo_cuccioli );

Slurping

Un assegnamento di un aggregato a una lista è sempre greedy, per cui assegnare a %cuccioli fa lo slurp di tutti i valori restanti di @_. Se il parametro $tipo fosse alla fine di @_, Perl produrrebbe un warning relativo all'assegnamento di un numero dispari di elementi ad un hash. Se volete, potete aggirare questo problema:

    sub mostra_cuccioli_per_tipo
    {
        my $tipo = pop;
        my %cuccioli = @_;

        ...
    }

... al costo però di una minore chiarezza. Lo stesso principio si applica naturalmente anche all'assegnamento ad un array come parametro. Usate i riferimenti (Riferimenti) per evitare appiattimenti e slurping indesiderati degli aggregati.

Aliasing

C'è una sottigliezza legata all'array @_; esso contiene infatti degli alias agli argomenti della funzione, e questo rende possibile modificarli direttamente. Per esempio:

    sub modifica_nome
    {
        $_[0] = reverse $_[0];
    }

    my $nome = 'Arancia';
    modifica_nome( $nome );
    say $nome;

    # stampa aicnarA

Quando modificate direttamente un elemento di @_ modificate anche il parametro originale. Fate attenzione a spacchettare @_ con cura.

Funzioni e Namespace

Ogni funzione è contenuta in un namespace (Package). Le funzioni senza un esplicito namespace—ovvero le funzioni che non sono dichiarate dopo un'istruzione package—appartengono al namespace main. Potete anche dichiarare una funzione in un altro namespace anteponendo un prefisso al suo nome:

    sub Estensioni::Matematica::somma {
        ...
    }

Questo codice dichiara la funzione e, se necessario, crea il namespace. Ricordate che i package di Perl 5 sono modificabili in qualunque punto del codice. Potete dichiarare una sola funzione con un determinato nome in un namespace. In caso contrario Perl 5 genera un warning di ridefinizione di una funzione. Potete disabilitare questo warning con no warnings 'redefine'—se siete sicuri che questo comportamento sia ciò che intendevate ottenere.

Per chiamare funzioni in altri namespace usate i loro nomi qualificati:

    package main;

    Estensioni::Matematica::somma( $scalare, $vettore );

Le funzioni di un namespace sono visibili al di fuori di esso attraverso i loro nomi qualificati. Dentro a un namespace, per chiamare una funzione dichiarata in quel namespace potete usare semplicemente il suo nome. Potete anche importare dei nomi da altri namespace.

Importazione

Quando caricate un modulo con il costrutto nativo use (Moduli), Perl chiama automaticamente un metodo di nome import() su tale modulo. Un modulo può fornire un proprio import() per rendere alcuni o tutti i simboli che definisce disponibili al package chiamante. Eventuali argumenti che seguono il nome del modulo nell'istruzione use vengono passati al metodo import() del modulo. Quindi:

    use strict;

... carica il modulo strict.pm e chiama strict->import() senza argomenti, mentre:

    use strict 'refs';
    use strict qw( subs vars );

... carica il modulo strict.pm, chiama strict->import( 'refs' ) e infine chiama strict->import( 'subs', 'vars' ).

use chiama implicitamente import() come descritto qui sopra, ma potete chiamare import() anche direttamente. L'esempio fatto con use è equivalente a:

    BEGIN
    {
        require strict;
        strict->import( 'refs' );
        strict->import( qw( subs vars ) );
    }

L'istruzione nativa use aggiunge un blocco BEGIN implicito che racchiude queste linee di codice in modo tale che la chiamata a import() avviene immediatamente dopo che il parser ha compilato l'intera istruzione use. Questo assicura che ogni simbolo importato sia visibile durante la compilazione del resto del programma. In caso contrario, le funzioni importate da altri moduli ma non dichiarate nel file corrente sarebbero interpretate come bareword, e violerebbero la direttiva strict.

Gestione degli Errori

All'interno di una funzione, potete ispezionare il contesto della chiamata alla funzione stessa con la funzione nativa caller. Quando non riceve argomenti, essa restituisce una lista di tre elementi contenente il nome del package chiamante, il nome del file contenente la chiamata e il numero della linea del file dove è avvenuta la chiamata:

    package main;

    main();

    sub main
    {
        mostra_info_chiamata();
    }

    sub mostra_info_chiamata
    {
        my ($package, $file, $linea) = caller();
        say "Chiamata da $package in $file:$linea";
    }

È possibile ispezionare L'intera catena di chiamate. Passate un singolo argomento intero n a caller() per ispezionare il contesto della chiamata del chiamante n-esimo nella catena. In altre parole, se mostra_info_chiamata() usasse caller(0), riceverebbe informazioni sulla chiamata da main(). Se usasse caller(1), riceverebbe informazioni sulla chiamata dall'inizio del programma.

L'argomento opzionale indica anche a caller di restituire ulteriori valori di ritorno, inclusi il nome della funzione e il contesto di numero della chiamata:

    sub mostra_info_chiamata
    {
        my ($package, $file, $linea, $funz) = caller(0);
        say "$funz chiamata da $package in $file:$linea";
    }

Il modulo standard Carp usa efficacemente questa tecnica per riportare errori e generare warning nelle funzioni. Quando viene usata al posto di die nel codice di una libreria, croak() solleva un'eccezione dal punto di vista del chiamante di tale codice. Analogamente, carp() genera un warning contenente il nome di file e numero di linea del chiamante (Generare i Warning).

Questo comportamento è particolarmente utile quando validate i parametri o le precondizioni di una funzione per indicare che il codice chiamante è in qualche modo errato.

Validazione degli Argomenti

Anche se in genere si adopera per fare ciò che desidera il programmatore, il Perl offre pochi modi nativi per testare la validità degli argomenti passati a una funzione. Potete valutare @_ in contesto scalare per verificare se il numero di parametri passati a una funzione è corretto:

    sub somma_numeri
    {
        croak 'Mi aspettavo due numeri, ne ho ricevuti: ' . @_
            unless @_ == 2;

        ...
    }

Il controllo dei tipi è più difficile, a causa delle conversioni di tipo in Perl indotte dagli operatori (Contesto). Il modulo CPAN Params::Validate offre la possibilità di controlli più rigorosi.

Argomenti Avanzati sulle Funzioni

Le funzioni sono alla base di molte funzionalità avanzate del Perl.

Conoscenza del Contesto

Le funzioni native di Perl 5 sanno se le avete invocate in contesto void, scalare o lista; la stessa cosa è possibile per le vostre funzioni. La funzione nativa chiamata erroneamente wantarray Vedete perldoc -f wantarray per una conferma. restituisce undef per indicare un contesto void, un valore falso per indicare un contesto scalare e un valore vero per indicare un contesto lista.

    sub sensibile_al_contesto
    {
        my $contesto = wantarray();

        return qw( Contesto lista )   if         $contesto;
        say    'Contesto void'    unless defined $contesto;
        return 'Contesto scalare' unless         $contesto;
    }

    sensibile_al_contesto();
    say my $scalare = sensibile_al_contesto();
    say sensibile_al_contesto();

Questo può essere particolarmente utile per evitare che delle funzioni producano dei valori di ritorno computazionalmente costosi in un contesto void. Alcune funzioni idiomatiche restituiscono una lista in contesto lista e il primo elemento della lista o un riferimento ad array in contesto scalare. Ricordate però che non esiste un'unica regola ottimale per l'uso di wantarray. Alcune volte è meglio scrivere funzioni separate per evitare l'ambiguità.

Nel Giusto Contesto

Le distribuzioni CPAN Want di Robin Houston e Contextual::Return di Damian Conway offrono molti modi di scrivere interfacce sensibili al contesto potenti ed usabili.

Ricorsione

Supponete di voler trovare un elemento in un array ordinato. Potreste iterare su ogni singolo elemento dell'array, confrontandolo con ciò che cercate, ma in media dovreste esaminare metà degli elementi dell'array. Un altro approccio consiste nel dividere l'array in due parti, prendere l'elemento di mezzo, confrontarlo e ripetere il processo sulla metà inferiore o superiore dell'array. Divide et impera. Quando non ci sono più elementi da ispezionare oppure avete trovato l'elemento cercato, vi fermate.

Un test automatico di questa tecnica potrebbe essere:

    use Test::More;

    my @elementi =
    (
        1, 5, 6, 19, 48, 77, 997, 1025, 7777, 8192, 9999
    );

    ok   esiste_elem(     1, @elementi ),
            'trovato primo elemento dell'array';
    ok   esiste_elem(  9999, @elementi ),
             'trovato ultimo elemento dell'array';
    ok ! esiste_elem(   998, @elementi ),
            'non trovato elemento che non è nell'array';
    ok ! esiste_elem(    -1, @elementi ),
            'non trovato elemento che non è nell'array';
    ok ! esiste_elem( 10000, @elementi ),
            'non trovato elemento che non è nell'array';

    ok   esiste_elem(    77, @elementi ),
            'trovato elemento in mezzo';
    ok   esiste_elem(    48, @elementi ),
            'trovato ultimo elemento di parte inferiore';
    ok   esiste_elem(   997, @elementi ),
            'trovato primo elemento di parte superiore';

    done_testing();

La ricorsione è un concetto che appare ingannevolmente semplice. Ogni chiamata a una funzione in Perl crea un nuovo blocco di attivazione, una struttura dati interna che rappresenta la chiamata stessa, e include l'ambiente lessicale della chiamata corrente alla funzione. Ciò significa che la funzione può richiamare se stessa o ricorrere.

Per poter eseguire con successo il test precedente, dovete scrivere una funzione chiamata esiste_elem() che sappia come chiamare se stessa dividendo ogni volta la lista in due parti:

    sub esiste_elem
    {
        my ($elem, @array) = @_;

        # termina ricorsione se non ci sono elementi tra cui cercare
        return unless @array;

        # arrotonda per difetto se il numero di elementi è dispari
        my $punto_in_mezzo = int( (@array / 2) - 0.5 );
        my $elem_in_mezzo  = $array[ $punto_in_mezzo ];

        # restituici vero se elemento trovato
        return 1 if $elem  == $elem_in_mezzo;

        # restituici falso se c'è un solo elemento
        return   if @array == 1;

        # chiama ricorsivamente su parte inferiore
        return esiste_elem(
            $elem, @array[0 .. $punto_in_mezzo ]
        ) if $elem < $elem_in_mezzo;

        # chiama ricorsivamente su parte superiore
        return esiste_elem(
             $elem, @array[ $punto_in_mezzo + 1 .. $#array ]
        );
    }

Anche se è possibile scrivere questo codice in modo procedurale e gestire voi stessi le parti della lista, l'approccio ricorsivo fa sì che sia il Perl a tenere traccia di questi dettagli.

Lessicali

Ogni nuova invocazione di una funzione crea la propria istanza di uno scope lessicale. Sebbene la dichiarazione di esiste_elem() crei un singolo scope per i lessicali $elem, @array, $punto_in_mezzo e $elem_in_mezzo, ogni chiamata a esiste_elem()—anche ricorsiva—memorizza i valori di tali lessicali separatamente.

Non solo esiste_elem() può chiamare se stesso, ma le variabili lessicali di ogni invocazione sono protette e separate:

    use Carp 'cluck';

    sub esiste_elem
    {
        my ($elem, @array) = @_;

        cluck "[$elem] (@array)";

        # segue altro codice
        ...
    }

Chiamate di Coda

Un punto critico della ricorsione è che dovete specificare correttamente le condizioni di terminazione se non volete che la vostra funzione chiami se stessa un numero infinito di volte. La funzione esiste_elem() contiene diverse istruzioni return proprio a questo scopo.

Perl offre un utile warning Deep recursion on subroutine (NdT: ricorsione profonda sulla funzione) quando sospetta la presenza di una ricorsione infinita. Il limite di 100 chiamate ricorsive è arbitrario, ma è spesso utile. Potete disabilitare questo warning con no warnings 'recursion' nello scope della chiamata ricorsiva.

Dato che ogni chiamata a funzione richiede un nuovo blocco di attivazione e dello spazio di memoria per i lessicali, il codice altamente ricorsivo potrebbe usare più memoria del codice iterativo. La eliminazione delle chiamate di coda può essere d'aiuto.

Una chiamata di coda è una chiamata a una funzione che restituisce direttamente il risultato della funzione chiamante. Le seguenti chiamate a esiste_elem():

    # chiama ricorsivamente su parte inferiore
    return esiste_elem(
        $elem, @array[0 .. $punto_in_mezzo ]
    ) if $elem < $elem_in_mezzo;

    # chiama ricorsivamente su parte superiore
    return esiste_elem(
         $elem, @array[ $punto_in_mezzo + 1 .. $#array ]
    );

... sono delle candidate all'eliminazione delle chiamate di coda. Questa ottimizzazione eviterebbe di ritornare alla chiamata corrente e poi alla chiamata precedente, ritornando invece subito alla chiamata precedente.

Purtroppo, Perl 5 non elimina automaticamente le chiamate di coda. Fatelo voi manualmente con una speciale forma dell'istruzione goto. Contrariamente alla forma che spesso produce dello spaghetti code, la goto a funzione sostituisce la chiamata di funzione corrente con quella ad un'altra funzione. Potete indicare una funzione sia per nome che per riferimento. Per passare degli argomenti diversi, impostate direttamente @_:

    # chiama ricorsivamente su parte inferiore
    if ($elem < $elem_in_mezzo)
    {
        @_ = ($elem, @array[0 .. $punto_in_mezzo ]);
        goto &esiste_elem;
    }

    # chiama ricorsivamente su parte superiore
    else
    {
        @_ = ($elem, @array[ $punto_in_mezzo + 1 .. $#array ] );
        goto &esiste_elem;
    }

Qualche volta le ottimizzazioni lasciano a desiderare dal punto di vista estetico.

Trappole e Funzionalità da Evitare

Perl 5 supporta ancora le invocazioni di funzione vecchio stile, ereditate da versioni precedenti del Perl. Mentre ora potete invocare le funzioni Perl per nome, le versioni precedenti di Perl richiedevano di invocarle con una "e commerciale" (&) come primo carattere. Perl 1 richiedeva di usare l'istruzione do:

    # stile superato; da evitare
    my $risultato = &calcola_risultato( 52 );

    # stile Perl 1; da evitare
    my $risultato = do calcola_risultato( 42 );

    # strano miscuglio; da evitare assolutamente
    my $risultato = do &calcola_risultato( 42 );

Se da una parte la sintassi Perl 1 è solo visivamente sgradevole, la forma con la "e commerciale" ha altri effetti sorprendenti. Per prima cosa, disabilita qualunque controllo sul prototipo. Inoltre, passa implicitamente l'esatto contenuto di @_ a meno che specifichiate voi stessi gli argomenti. Entrambe possono causare strani comportamenti del codice.

Un'ultima trappola viene dall'omissione delle parentesi nelle chiamate di funzione. Il parser di Perl 5 usa diverse euristiche per risolvere le bareword ambigue e per determinare il numero di parametri passati a una funzione. Tali euristiche possono sbagliare:

    # attenzione; contiene un insidioso errore
    ok esiste_elem 1, @elementi, 'trovato primo elemento';

La chiamata a esiste_elem() si ingoia la descrizione del test, intesa invece come il secondo argomento a ok(). Poiché il secondo parametro di esiste_elem() fa slurping, questo problema può rimanere nascosto fino a quando Perl produce dei warning relativi al confronto tra un valore non-numerico (la descrizione del test, che non può essere convertita in un numero) e l'elemento nell'array.

Mentre l'uso indiscriminato di parentesi può peggiorare la leggibilità, un loro uso ponderato può chiarire il significato del codice e rendere improbabili alcuni errori insidiosi.

Scope

In Perl il concetto di scope si riferisce al tempo di vita e alla visibilità delle entità con un nome. Ogni entità Perl con un nome (una variabile, una funzione) ha uno scope. Il meccanismo di scope favorisce l'incapsulamento—che consiste nel mantenere insieme i concetti collegati ed evitare che essi fuoriescano dall'ambito in cui il programmatore li ha confinati.

Scope Lessicale

Lo scope lessicale è uno scope visibile mente leggete un programma. Il compilatore Perl risolve questo tipo di scope durante la compilazione. Un blocco delimitato da parentesi graffe crea un nuovo scope, sia che si tratti di un blocco a se stante, di un blocco di un costrutto di ciclo, del blocco di una dichiarazione sub, di un blocco eval o di qualunque altro blocco purché non sia di quotatura.

Lo scope lessicale determina la visibilità delle variabili dichiarate con my—le variabili dette lessicali. Una variabile lessicale dichiarata in uno scope è visibile in quello scope e negli scope annidati, ma è invisibile agli scope dello stesso livello e a quelli esterni:

    # scope lessicale esterno
    {
        package Robot::Maggiordomo

        # scope lessicale interno
        my $livello_batteria;

        sub pulisci_stanza
        {
            # scope ancora piu' interno
            my $timer;

            do {
                # scope lessicale ancora piu' interno
                my $paletta;
                ...
            } while (@_);

            for (@_)
            {
                # altro scope lessicale ancora piu' interno
                my $straccio;
                ...
            }
        }
    }

... $livello_batteria è visibile in tutti e quattro gli scope. $timer è visibile nel metodo, nel blocco do e nel ciclo for. $paletta è visibile soltanto nel blocco do e $straccio è visibile soltanto nel ciclo for.

Dichiarare un lessicale in uno scope con lo stesso nome di un lessicale in uno scope più esterno nasconde, o oscura, il lessicale esterno nello scope interno. Spesso, è proprio ciò che desiderate:

    my $nome = 'Jacob';

    {
        my $nome = 'Edward';
        say $nome;
    }

    say $nome;

Collisioni di Nomi

L'oscuramento di lessicali può essere accidentale. Limitate lo scope delle variabili e l'annidamento degli scope per ridurre questo rischio.

Questo programma stampa Edward e quindi Jacob Familiari dell'autore, non i noti vampiri., sebbene ridichiarare una variabile lessicale con lo stesso nome e lo stesso tipo nello stesso scope lessicale produca un messaggio di warning. La possibilità di oscurare un lessicale è una funzionalità dell'incapsulamento.

Ci sono alcune sottigliezze relative alle dichiarazioni di lessicali, per esempio quando una variabile lessicale viene usata come variabile iteratore di un ciclo for. La sua dichiarazione è al di fuori del blocco del ciclo, ma il suo scope è all'interno del blocco stesso:

    my $gatto = 'Brad';

    for my $gatto (qw( Jack Daisy Petunia Tuxedo Choco ))
    {
        say "L'iteratore gatto ha il valore $gatto";
    }

    say "Il gatto esterno ha sempre valore $gatto";

Analogamente, given (Given/When) crea un topic lessicale (come my $_) nel proprio blocco:

    $_ = 'fuori';

    given ('dentro')
    {
        say;
        $_ = 'questo assegnamento e' inutile';
    }

    say;

... in modo che l'uscita dal blocco ripristina il valore precedente di $_.

Le funzioni—sia con nome che anonime—forniscono uno scope lessicale al proprio corpo. Grazie a questo è possibile definire le closure (Chiusure).

Lo Scope Our

Dentro a un certo scope, potete dichiarare un alias a una variabile di package con l'istruzione our. Come my, our impone uno scope lessicale all'alias. Il nome qualificato è disponibile ovunque, ma l'alias lessicale è visibile solo all'interno dello scope.

our è particolarmente utile per le variabili di package globali come $VERSION e $AUTOLOAD.

Scope Dinamico

Lo scope dinamico è simile allo scope lessicale per ciò che riguarda le sue regole di visibilità, ma la ricerca dei nomi, invece di procedere dagli scope più interni a quelli più esterni durante la compilazione, procede all'indietro nei contesti di chiamata. Anche se una variabile di package globale è visibile in tutti gli scope, il suo valore cambia a seconda della sua localizzazione e relativo assegnamento:

    our $scope;

    sub interno
    {
        say $scope;
    }

    sub main
    {
        say $scope;
        local $scope = 'scope del main()';
        intermedio();
    }

    sub intermedio
    {
        say $scope;
        interno();
    }

    $scope = 'scope esterno';
    main();
    say $scope;

Il programma inizia con la dichiarazione di una variabile our, $scope, e di tre funzioni. Quindi, fa un assegnamento a $scope e chiama la funzione main().

Dentro a main(), il programma stampa il valore corrente di $scope, scope esterno, e poi localizza la variabile. Questo cambia la visibilità del simbolo nello scope lessicale corrente così come in ogni funzione chiamata dallo scope lessicale corrente. Quindi, $scope contiene scope del main() sia nel corpo di intermedio() che di interno(). Quando main() termina perché il controllo di flusso raggiunge la fine del suo blocco, Perl ripristina il valore originale della variabile $scope localizzata. Quindi, l'ultimo say stampa di nuovo scope esterno.

Le variabili di package e quelle lessicali hanno regole di visibilità e meccanismi di memorizzazione diversi nel Perl. Ogni scope che contiene variabili lessicali ha una speciale struttura dati chiamata pad lessicale o lexpad che può memorizzare i valori delle variabili lessicali incluse nello scope. Ogni volta che il controllo di flusso entra in uno di questi scope, Perl crea un nuovo lexpad per i valori delle variabili lessicali in quella particolare chiamata. Questo determina la corretta esecuzione delle funzioni, specialmente nelle chiamate ricorsive (Ricorsione).

Ogni package ha un'unica tabella dei simboli che contiene sia le variabili di package che le funzioni con nome. L'importazione (Importazione) si basa sull'ispezione e la manipolazione di tale tabella dei simboli. Lo stesso vale per local. Potete localizzare solo le variabili globali predefinite e di package—e non le variabili lessicali.

local è particolarmente utile con le variabili globali predefinite. Per esempio, il separatore di record in input $/ determina quanti dati vengono letti da un filehandle da un'operazione readline. $!, la variabile di errore di sistema, contiene il numero di errore della chiamata di sistema più recente. $@, la variabile Perl di errore di eval, contiene l'errore generato dalla operazione eval più recente. $|, la variabile di flush automatico, determina se Perl deve eseguire il flush del filehandle corrente impostato con select dopo ogni operazione di scrittura.

localizzare queste variabili in uno scope il più ristretto possibile limita gli effetti delle vostre modifiche. In questo modo, potete evitare strani comportamenti in altre parti del vostro codice.

Scope di Variabili State

Perl 5.10 ha aggiunto un nuovo scope per supportare l'istruzione nativa state. Lo scope di state è simile allo scope lessicale per quanto riguarda la visibilità, ma impone un'unica initializzazione e la persistenza del valore:

    sub contatore
    {
        state $cont = 1;
        return $cont++;
    }

    say contatore();
    say contatore();
    say contatore();

Nella prima chiamata a contatore, Perl esegue l'unica inizializzazione di $cont. Nelle chiamate successive, $cont mantiene il suo valore precedente. Questo programma stampa quindi 1, 2 e 3. Se cambiate state in my il programma stamperà 1, 1 e 1.

Potete anche usare un'espressione per impostare il valore iniziale di una variabile state:

    sub countatore
    {
        state $cont = shift;
        return $cont++;
    }

    say contatore(2);
    say contatore(4);
    say contatore(6);

Sebbene a prima vista si potrebbe pensare che l'output del codice sia 2, 4, e 6, tale output è invece 2, 3, e 4. La prima chiamata alla funzione contatore inizializza la variabile $cont. Nelle chiamate successive la parte di inizializzazione non viene più eseguita.

state può essere utile sia per impostare un valore di default che per predisporre una cache, ma se la usate fate attenzione ad avere compreso il suo comportamento con le inizializzazioni:

    sub contatore
    {
        state $cont = shift;
        say 'Il secondo arg ha valore: ', shift;
        return $cont++;
    }

    say contatore(2, 'due');
    say contatore(4, 'quattro');
    say contatore(6, 'sei');

In questo programma il contatore stampa 2, 3 e 4 come vi aspettate, ma i valori del presunto secondo argomento nelle chiamate a contatore() sono due, 4 e 6—dato che lo shift del primo argumento ha luogo soltanto nella prima chiamata a contatore(). Per evitare questo problema potete cambiare l'interfaccia di contatore(), oppure gestirlo nel modo seguente:

    sub contatore
    {
        my ($valore_iniziale, $testo) = @_;

        state $cont = $valore_iniziale;
        say "Il secondo arg ha valore: $testo";
        return $cont++;
    }

    say contatore(2, 'due');
    say contatore(4, 'quattro');
    say contatore(6, 'sei');

Funzioni Anonime

Una funzione anonima è una funzione senza nome. Per il resto si comporta esattamente come una funzione con nome—potete invocarla, passarle degli argomenti, farle restituire dei valori e copiare riferimenti ad essa. Tuttavia, l'unico modo per potervi accedere è attraverso un riferimento (Riferimenti a Funzioni).

Un idioma comune in Perl 5 noto come tabella di dispatch usa gli hash per associare i valori di un input a dei comportamenti specifici:

    my %dispatch =
    (
        piu  => \&somma_due_numeri,
        meno => \&sottrai_due_numeri,
        per  => \&moltiplica_due_numeri,
    );

    sub somma_due_numeri      { $_[0] + $_[1] }
    sub sottrai_due_numeri    { $_[0] - $_[1] }
    sub moltiplica_due_numeri { $_[0] * $_[1] }

    sub dispatch
    {
        my ($sinistro, $op, $destro) = @_;

        return unless exists $dispatch{ $op };

        return $dispatch{ $op }->( $sinistro, $destro );
    }

La funzione dispatch() riceve argomenti nella forma (2, 'per', 2) e restituisce il risultato dell'operazione.

Dichiarazione di Funzioni Anonime

L'istruzione nativa sub usata senza un nome crea e restituisce il riferimento a una funzione anonima. Potete usare questo riferimento ovunque è ammissibile usare un riferimento a una funzione con nome, per esempio per dichiarare le funzioni di una tabella di dispatch direttamente:

    my %dispatch =
    (
        piu     => sub { $_[0]  + $_[1] },
        meno    => sub { $_[0]  - $_[1] },
        per     => sub { $_[0]  * $_[1] },
        diviso  => sub { $_[0]  / $_[1] },
        elevato => sub { $_[0] ** $_[1] },
    );

Dispatch Difensivo

Questa tabella di dispatch presenta un certo grado di sicurezza; soltanto le funzioni mappate nella tabella stessa sono disponibili per essere chiamate dagli utenti. Se la vostra funzione di dispatch assumesse ciecamente che la stringa passata come nome dell'operatore corrisponde direttamente al nome di una funzione da chiamare, un utente malintenzionato potrebbe chiamare qualunque funzione in qualunque namespace passando 'Funzioni::Interne::funzione_maligna'.

Potreste anche vedere delle funzioni anonime passate come argomenti ad altre funzioni:

    sub invoca_funzione__anonima
    {
        my $funz = shift;
        return $funz->( @_ );
    }

    sub funzione_con_nome
    {
        say 'Sono una funzione con nome!';
    }

    invoca_funzione__anonima( \&funzione_con_nome );
    invoca_funzione__anonima( sub { say 'Indovina chi sono!' } );

Nomi di Funzioni Anonime

Dato un riferimento a una funzione, è possibile determinare se la funzione ha un nome oppure è anonima usando l'introspezione oppure sub_name del modulo CPAN Sub::Identify.:

    package MostraChiamante;

    sub mostra_chiamante
    {
        my ($package, $file, $linea, $sub) = caller(1);
        say "Chiamata da $sub in $package:$file:$linea";
    }

    sub main
    {
        my $sub_anonima = sub { mostra_chiamante() };
        mostra_chiamante();
        $sub_anonima->();
    }

    main();

Il risultato potrebbe cogliervi un po' di sorpresa:

    Chiamata da MostraChiamante::main
             in MostraChiamante:chiamante_anonimo.pl:20
    Chiamata da MostraChiamante::__ANON__
             in MostraChiamante::chiamante_anonimo.pl:17

La stringa __ANON__ nella seconda linea dell'output mostra che la funzione anonima non ha un nome che Perl possa identificare. Questo può rendere il debugging più complicato. La funzione subname() del modulo CPAN Sub::Name vi permette di associare dei nomi alle funzioni anonime:

    use Sub::Name;
    use Sub::Identify 'sub_name';

    my $anonima  = sub {};
    say sub_name( $anonima );

    my $con_nome = subname( 'pseudo-anonima', $anonima );
    say sub_name( $con_nome );
    say sub_name( $anonima );

    say sub_name( sub {} );

Questo programma produce l'output:

    __ANON__
    pseudo-anonima
    pseudo-anonima
    __ANON__

Notate che entrambi i riferimenti sono relativi alla stessa funzione anonima sottostante. Chiamando subname() con un riferimento a una funzione anonima, il nome di tale funzione viene modificato in modo tale che tutti gli altri riferimenti ad essa vedono il nuovo nome.

Funzioni Anonime Implicite

Perl 5 permette la dichiarazione implicita di funzioni anonime attraverso l'uso dei prototipi (Prototipi). Sebbene questa funzionalità ufficialmente sia presente per permettere ai programmatori di definire una propria sintassi come fanno map e eval, un esempio interessante è l'uso di funzioni ritardate che non sembrano funzioni.

Considerate il modulo CPAN Test::Fatal, che riceve una funzione anonima come primo argomento della sua funzione exception:

    use Test::More;
    use Test::Fatal;

    my $muore = exception { die 'Sto morendo!' };
    my $vive  = exception { 1 + 1 };

    like( $muore, qr/Sto morendo/, 'die() causa la morte'   );
    is(   $vive,  undef,           'la somma lascia vivere' );

    done_testing();

Potete riscrivere questo codice in modo più verboso:

    my $muore = exception( sub { die 'Sto morendo!' } );
    my $vive  = exception( sub { 1 + 1 } );

...o passare riferimenti a funzioni con nome:

    sub muore { die 'Sto morendo!' }
    sub vive  { 1 + 1 }

    my $muore = exception \&muore;
    my $vive  = exception \&vive;

    like( $muore, qr/Sto morendo/, 'die() causa la morte'   );
    is(   $vive,  undef,           'la somma lascia vivere' );

... ma non potete invece passare tali riferimenti come scalari:

    my $rif_a_muore = \&muore;
    my $rif_a_vive  = \&vive;

    # BUG: non funziona
    my $muore   = exception $rif_a_muore;
    my $vive    = exception $rif_a_vive;

... perché il prototipo modifica il modo in cui il parser di Perl 5 interpreta questo codice. Non potendo essere sicuro al 100% di che cosa conterranno $muore e $vive, il parser solleva un'eccezione.

    Type of arg 1 to Test::Fatal::exception
       must be block or sub {} (not private variable)

Ricordate anche che una funzione che riceve una funzione anonima come il primo di diversi argomenti non ammette la virgola dopo il blocco della funzione:

    use Test::More;
    use Test::Fatal 'dies_ok';

    dies_ok { die 'La Forza sia con te!' }
            'Non mi piacciono le citazioni dai film';

Questo piccolo neo, dovuto a un'idiosincrasia del parser di Perl 5, può occasionalmente causare confusione nell'uso di una sintassi altrimenti utile. La pulizia sintattica che deriva dalla promozione di blocchi isolati a funzioni anonime può essere vantaggiosa, ma usatela con parsimonia e documentate la vostra API con cura.

Chiusure

Ogni volta che il controllo di flusso entra in una funzione, tale funzione riceve anche un nuovo ambiente che rappresenta lo scope lessicale di quella particolare chiamata (Scope). Questo vale anche per le funzioni anonime (Funzioni Anonime). Le implicazioni sono importanti. Nell'informatica di base, il termine funzioni di ordine superiore si riferisce a funzioni che manipolano altre funzioni. Le chiusure consentono di sfruttare pienamente le potenzialità di tali funzioni.

Creazione di Chiusure

Una chiusura è una funzione che usa le variabili lessicali di uno scope esterno. Probabilmente avete già creato e utilizzato delle chiusure senza rendervene conto:

    package Chiusura::Invisibile;

    my $nomefile = shift @ARGV;

    sub dammi_il_nomefile { return $nomefile }

Se questo codice vi sembra ovvio, è un buon inizio! Naturalmente la funzione dammi_il_nomefile() può vedere il lessicale $nomefile. È propio il modo in cui funziona lo scope!

Immaginate di voler iterare su una lista di elementi senza dover gestire voi stessi l'iteratore. Potete creare una funzione che restituisce una funzione che, quando viene invocata, restituisce il prossimo elemento dell'iterazione:

    sub crea_iteratore
    {
        my @elementi = @_;
        my $cont     = 0;

        return sub
        {
            return if $cont == @elementi;
            return $elementi[ $cont++ ];
        }
    }

    my $cugini = crea_iteratore(
        qw( Riccardo Ale Carlo Enrico Camilla Maria Cristina )
    );

    say $cugini->() for 1 .. 5;

Sebbene crea_iteratore() sia terminata, la funzione anonima memorizzata in $cugini ha fatto una chiusura sui valori delle due variabili così come essi si presentavano all'interno dell'invocazione di crea_iteratore(). Tali valori, dunque, persistono (Reference Count).

Poiché ogni invocazione di crea_iteratore() crea un nuovo ambiente lessicale, ogni sub anonima che viene creata e restituita fa una chiusura su un proprio ambiente lessicale unico:

    my $zie = crea_iteratore(
        qw( Carolina Filomena Viviana Silvia Monica Lara )
    );

    say $cugini->();
    say $zie->();

Dato che crea_iteratore() non restituisce questi lessicali né per valore, né per riferimento, nessun'altra parte del codice Perl può accedervi, eccetto la chiusura. Essi risultano incapsulati allo stesso modo degli altri lessicali, e quindi solo il codice che condivide con essi un ambiente lessicale può accedervi. Questo idioma fornisce un incapsulamento migliore di quello che fornirebbe invece una variabile globale di file o di package:

    {
        my $variabile_privata;

        sub imposta_privata { $variabile_privata = shift }
        sub leggi_privata { $variabile_privata }
    }

Ricordate che non potete annidare le funzioni con nome. Le funzioni con nome hanno uno scope globale al package. La condivisione di ogni variabile lessicale condivisa tra funzioni annidate viene perduta quando la funzione più esterna distrugge il suo primo ambiente lessicale Se questo vi sembra complicato, immaginate come dev'essere stato complicato implementarlo..

Invasione della Privacy

Il modulo CPAN PadWalker vi permette di violare l'incapsulamento lessicale, ma chiunque lo usi introducendo dei bug nel vostro codice si guadagna di diritto l'onore di risolvere tali bug senza il vostro aiuto.

Uso delle Chiusure

Iterare su una lista di dimensione fissata con una chiusura è interessante, ma le chiusure possono fare molto altro, come iterare su una lista che è troppo costosa da calcolare o troppo grande da tenere in memoria tutta in una volta. Considerate una funzione per creare la serie di Fibonacci man mano che avete bisogno dei suoi elementi. Invece di ricalcolare la serie ricorsivamente, usate una cache e siate pigri, creando gli elementi solo quando vi servono:

    sub gen_fib
    {
        my @seriefib = (0, 1);

        return sub
        {
            my $elem = shift;

            if ($elem >= @seriefib)
            {
                for my $calc (@seriefib .. $elem)
                {
                    $seriefib[$calc] = $seriefib[$calc - 2]
                                     + $seriefib[$calc - 1];
                }
            }
            return $seriefib[$elem];
        }
    }

Ogni chiamata alla funzione restituita da gen_fib() riceve un argomento, l'indice dell'n-esimo elemento della serie di Fibonacci. La funzione genera tutti i valori precedenti necessari della serie, li memorizza in cache e restituisce l'elemento richiesto—ritardando le computazioni fino a quando non sono assolutamente necessarie. C'è un pattern specifico di caching intrecciato con la serie numerica. Che cosa succede se estraete il codice specifico di caching (inizializzazione della cache, escuzione di codice dedicato al popolamento deglii elementi arbitrari della cache e restituzione di un valore calcolato o estratto dalla cache) in una funzione genera_chiusura_cache()?

    sub genera_chiusura_cache
    {
        my ($calcola_elem, @cache) = @_;

        return sub
        {
            my $elem = shift;

            $calcola_elem->($elem, \@cache)
                unless $elem < @cache;

            return $cache[$elem];
        };
    }

Riordina, Applica e Filtra

Le funzioni native map, grep e sort sono anch'esse delle funzioni di ordine superiore.

Ora gen_fib() diventa:

    sub gen_fib
    {
        my @seriefib = (0, 1, 1);

        return genera_chiusura_cache(
            sub
            {
                my ($elem, $seriefib) = @_;

                for my $calc ((@$seriefib - 1) .. $elem)
                {
                    $seriefib->[$calc] = $seriefib->[$calc - 2]
                                       + $seriefib->[$calc - 1];
                }
            },
            @seriefib
        );
    }

Il programma funziona come prima, ma l'uso di riferimenti a funzione e chiusure separa il codice di inizializzazione della cache dal calcolo del prossimo numero della serie di Fibonacci. Personalizzare il comportamento del codice—in questo caso, di genera_chiusura_cache()—passando una funzione vi mette a disposizione una enorme flessibilità.

Chiusure e Applicazione Parziale

Le chiusure possono anche limitare la generalità indesiderata di una funzione. Considerate una funzione che riceve molti parametri:

    sub prepara_coppa
    {
        my %argomenti = @_;

        my $gelato   = dammi_gelato( $argomenti{gelato} );
        my $banana   = dammi_banana( $argomenti{banana} );
        my $sciroppo = dammi_sciroppo( $argomenti{sciroppo} );
        ...
    }

Avere un'enorme quantità di personalizzazioni possibili può essere giusto per una elegante gelateria in centro città, ma per un venditore ambulante che serve solo gelato alla vaniglia su banane di qualità Cavendish, ogni chiamata a prepara_coppa() passa alcuni argometi che non cambiano mai.

Una tecnica detta applicazione parziale vi permette di specificare alcuni degli argumenti a una funzione e di fornire i restanti in un momento successivo. Racchiudete la chiamata alla funzione in una chiusura e passatele i parametri che volete fissare.

Considerate il venditore ambulante che serve solo gelato alla vaniglia su banane di qualità Cavendish:

    my $prepara_coppa_ambulante = sub
    {
        return prepara_coppa( @_,
            gelato => 'Vaniglia',
            banana => 'Cavendish',
        );
    };

Invece di chiamare direttamente prepara_coppa(), invocate il riferimento a funzione $prepara_coppa_ambulante e passate soltanto gli argomenti interessanti, senza dovervi preoccupare di dimenticare o di non passare correttamente quelli fissi Potete anche usare il modulo CPAN Sub::Install per importare questa funzione direttamente in un altro namespace..

Questo è solo un assaggio di quello che potete fare con le funzioni di ordine superiore. Il libro Higher Order Perl di Mark Jason Dominus è il riferimento bibliografico canonico per trattare funzioni e chiusure come oggetti di prima classe. Potete leggerlo online all'URL http://hop.perl.plover.com/.

State e Chiusure

Le chiusure (Chiusure) sfruttano lo scope lessicale (Scope) per mediare l'accesso a variabili semi-private. Anche le funzioni con nome possono sfruttare i legami imposti dallo scope lessicale:

    {
        my $sicurezza = 0;

        sub abilita_sicurezza    { $sicurezza = 1 }
        sub disabilita_sicurezza { $sicurezza = 0 }

        sub fai_qualcosa_di_straordinario
        {
            return if $sicurezza;
            ...
        }
    }

L'incapsulamento delle funzioni che (dis)attivano la sicurezza permette alle tre funzioni di condividere uno stato senza esporre la variabile lessicale direttamente al codice esterno. Questo idioma funziona bene nei casi in cui il codice esterno deve poter cambiare lo stato interno, ma è un po' ingombrante quando tale stato deve essere gestito da una singola funzione.

Immaginate che ogni cento clienti della vostra gelateria, uno riceva gratis un'aggiunta di panna:

    my $conta_cli = 0;

    sub servi_cliente
    {
        $conta_cli++;

        my $ordine = shift;

        aggiungi_panna($ordine) if $conta_cli % 100 == 0;

        ...
    }

Questo approccio funziona, ma creare un nuovo scope lessicale per un'unica funzione introduce più complessità di quanto sembri necessario. L'istruzione nativa state vi permette di dichiarare una variabile con scope lessicale il cui valore persiste tra un'invocazione e l'altra:

    sub servi_cliente
    {
        state $conta_cli = 0;
        $conta_cli++;

        my $ordine = shift;
        aggiungi_panna($ordine)
            if ($conta_cli % 100 == 0);

        ...
    }

Dovete attivare questa funzionalità usando un modulo come Modern::Perl, la direttiva feature (Direttive) o richiedendo una versione specifica di Perl che sia la 5.10 o successiva (per esempio, con use 5.010; o use 5.012;).

state funziona anche nelle funzioni anonime:

    sub crea_contatore
    {
        return sub
        {
             state $cont = 0;
             return $cont++;
         }
    }

... anche se questo approccio non presenta molti vantaggi evidenti.

State e Pseudo-State

A partire da Perl 5.10 è stata deprecata una tecnica usata con le versioni precedenti del Perl per emulare il comportamento di state. Una funzione con nome poteva fare una chiusura sul suo scope lessicale precedente abusando di un'idiosincrasia dell'implementazione. L'aggiunta di un condizionale postfisso che valutasse a falso in coda a una dichiarazione my evitava di reinizializzare una variabile lessicale a undef o al suo valore iniziale.

Adesso la presenza di un'espressione condizionale postfissa come modificatore di una dichiarazione di variabile lessicale viene deprecato con un warning. In effetti, con questa tecnica è troppo facile introdurre inavvertitamente dei bug nel codice; usate invece state dove è possibile o in alternativa una vera chiusura. Riscrivete in modo corretto questo idioma quando lo incontrate:

    sub cattivo_stato
    {
        # my $contatore  = 1 if 0; # DEPRECATO; non usatelo
        state $contatore = 1;      # usate invece questo

        ...
    }

Attributi

Le entità Perl con un nome—variabili e funzioni—possono essere associate a dei metadati aggiuntivi in forma di attributi. Gli attributi sono nomi e valori arbitrari usati da determinati tipi di metaprogrammazione (Generazione di Codice).

La sintassi per la dichiarazione degli attributi è poco elegante e usare gli attributi in modo efficace è più un'arte che una scienza. La maggior parte dei programmi evita di utilizzarli, ma quando sono usati bene essi possono offrire vantaggi in termini di chiarezza e manutenibilità.

Uso degli Attributi

Un attributo semplice è un identificatore preceduto dal carattere due punti e associato a una dichiarazione:

    my $fortezza         :nascosto;

    sub eruzione_vulcano :ProgettoScientifico { ... }

Queste dichiarazioni causano l'invocazione degli handler di attributi di nome nascosto e ScienceProject, se esistono per il tipo appropriato (rispettivamente, scalare e funzione). Tali handler possono fare qualunque cosa. Se non esistono degli handler appropriati, Perl solleva un'eccezione durante la compilazione.

Gli attributi possono specificare una lista di parametri. Perl tratta tali parametri come liste composte da sole stringhe costanti. Il modulo CPAN Test::Class fa un buon uso degli attributi parametrici:

    sub configura_test            :Test(setup)    { ... }
    sub creazione_test_automatico :Test(10)       { ... }
    sub termina_test              :Test(teardown) { ... }

L'attributo Test identifica i metodi che contengono asserzioni di test e specifica opzionalmente il numero di asserzioni che tali metodi intendono eseguire. Anche se l'introspezione (Riflessione) di queste classi, guidata da buone euristiche, potrebbe identificare appropriatamente i metodi di test, l'attributo :Test rende più chiare le vostre intenzioni.

I parametri setup e teardown permettono alle classi di test di definire i propri metodi di supporto senza preoccuparsi dei conflitti con metodi analoghi in altre classi. In questo modo si separa il problema di specificare ciò che una classe deve fare da come funzionano altre classi, e in questo modo si offre molta flessibilità al programmatore.

Attributi in Pratica

Anche il framework web Catalyst usa gli attributi per determinare la visibilità e il comportamento di alcuni metodi all'interno di applicazioni web.

Svantaggi degli Attributi

Gli attributi hanno anche degli svantaggi. La direttiva canonica per usare gli attributi, attributes, ha catalogato la propria interfaccia come sperimentale per molti anni. Il modulo Attribute::Handlers di Damian Conways integrato nella distribuzione semplifica la loro implementazione. Attribute::Lexical di Andrew Main è un approccio più recente. Ovunque sia possibile usarli, entrambi sono preferibili a attributes.

La peggiore caratteristica degli attributi è la loro propensione a produrre strani effetti sintattici a distanza. Dato un frammento di codice con attributi, come potete prevedere i loro effetti? Una buona documentazione può essere d'aiuto, ma se la dichiarazione apparentemente innocente di una variabile lessicale memorizza da qualche parte un riferimento a tale variabile, le vostre attese sul suo tempo di vita potrebbero rivelarsi sbagliate. Analogamente, un handler potrebbe racchiudere a vostra insaputa una funzione in un'altra funzione e sostituirla nella tabella dei simboli—pensate a un attributo :memoize che invochi automaticamente il modulo Memoize integrato nella distribuzione.

Gli attributi sono a vostra disposizione per risolvere alcuni problemi ostici. Possono essere molto utili, se usati in modo appropriato—ma la maggior parte dei programmi non ne ha bisogno.

AUTOLOAD

Perl non richiede che dichiariate ciascuna funzione prima di chiamarla e fa del suo meglio per chiamare una funzione anche se non esiste. Considerate questo programma:

    use Modern::Perl;

    cuoci_crostata( ripieno => 'mele' );

Quando lo eseguite, Perl solleva un'eccezione di chiamata a una funzione cuoci_crostata() non definita.

Ora aggiungete una funzione di nome AUTOLOAD():

    sub AUTOLOAD {}

Se rieseguite il programma, non succede nulla di evidente. Perl chiama la funzione AUTOLOAD() di un package—se esiste—ogni volta che il normale meccanismo di chiamata fallisce. Modificate AUTOLOAD() in modo da stampare un messaggio:

    sub AUTOLOAD { say 'Sono in AUTOLOAD()!' }

... per dimostrare che viene chiamata.

Funzionalità Base di AUTOLOAD

La funzione AUTOLOAD() riceve direttamente in @_ gli argomenti che erano stati passati alla funzione non definita, mentre il nome della funzione non definita è memorizzato nella variabile globale di package $AUTOLOAD. Potete manipolare questi argomenti a vostro piacimento:

    sub AUTOLOAD
    {
        our $AUTOLOAD;

        # stampa gli argomenti separati da virgola
        local $" = ', ';
        say "Sono in AUTOLOAD(@_) per colpa di $AUTOLOAD!"
    }

Con la dichiarazione our (Lo Scope Our), lo scope della variabile diventa il corpo della funzione. La variabile contiene il nome qualificato della funzione non definita (in questo caso, main::cuoci_crostata). Potete rimuovere il nome del package con un'espressione regolare (Espressioni Regolari e Matching):

    sub AUTOLOAD
    {
        my ($nome) = our $AUTOLOAD =~ /::(\w+)$/;

        # stampa gli argomenti separati da virgola
        local $" = ', ';
        say "Sono in AUTOLOAD(@_) per colpa di $nome!"
    }

La chiamata originale riceve ciò che viene restituito da AUTOLOAD():

    say funzione_misteriosa( -1 );

    sub AUTOLOAD { return 'mu' }

Gli esempi visti finora si sono limitati ad intercettare le chiamate a funzioni non definite. Naturalmente potete fare di più.

Redirezionare dei Metodi in AUTOLOAD()

Un pattern diffuso nella programmazione OO (Moose) è di avere un oggetto che delega o fa da proxy per alcuni metodi di un altro oggetto, spesso contenuto o comunque accessibile dal primo. Un proxy di logging può aiutarvi a fare debugging:

    package Proxy::Log;

    sub new
    {
        my ($classe, $delegata) = @_;
        bless \$delegata, $classe;
    }

    sub AUTOLOAD
    {
        my ($nome) = our $AUTOLOAD =~ /::(\w+)$/;
        Log::chiamata_metodo( $nome, @_ );

        my $self = shift;
        return $$self->$nome( @_ );
    }

La funzione AUTOLOAD() estrae il nome del metodo non definito e fa il log della sua chiamata. Quindi, ricava l'oggetto delegato dereferenziando un riferimento scalare battezzato e, infine, invoca il metodo sull'oggetto delegato passandogli i parametri ricevuti.

Generazione di Codice in AUTOLOAD()

Questa doppia chiamata è facile da scrivere, ma inefficiente. L'esecuzione di ogni chiamata di metodo sull'oggetto che fa da proxy deve fallire per entrare in AUTOLOAD(). Potete pagare questa penalità una sola volta, installando nuovi metodi nella classe che fa da proxy man mano che il programma li richiede:

    sub AUTOLOAD
    {
        my ($nome) = our $AUTOLOAD =~ /::(\w+)$/;

        my $metodo = sub
        {
            Log::chiamata_metodo( $nome, @_ );

            my $self = shift;
            return $$self->$nome( @_ );
        }

        no strict 'refs';
        *{ $AUTOLOAD } = $metodo;
        return $metodo->( @_ );
    }

Il corpo della AUTOLOAD() precedente è diventato una chiusura (Chiusure) legata con il nome del metodo non definito. Installare tale chiusura nell'appropriata tabella dei simboli permette alle chiamate successive di quel metodo di trovare la chiusura che avete creato (ed evitare AUTOLOAD()). Infine, la chiusura invoca direttamente il metodo e restituisce il risultato.

Sebbene questo approccio sia più pulito e quasi sempre più trasparente rispetto a gestire il comportamento direttamente in AUTOLOAD(), il codice chiamato da AUTOLOAD() ha la possibilità di scoprire che la chiamata è passata attraverso AUTOLOAD(). Infatti, una semplice chiamata a caller() rivela la doppia chiamata di entrambe le tecniche descritte qui sopra. Se da una parte tener conto di questo può essere visto come una violazione dell'incapsulamento, lo stesso si potrebbe dire dell'esporre i dettagli di come un oggetto fornisce un metodo.

Alcuni autori usano una chiamata di coda (Chiamate di coda) per sostituire la chiamata corrente di AUTOLOAD() con una chiamata al metodo destinatario:

    sub AUTOLOAD
    {
        my ($nome) = our $AUTOLOAD =~ /::(\w+)$/;
        my $metodo = sub { ... }

        no strict 'refs';
        *{ $AUTOLOAD } = $metodo;
        goto &$metodo;
    }

Questo ha lo stesso effetto di chiamare direttamente $metodo, eccetto che AUTOLOAD() non compare più nella lista di chiamate generate da caller(), e dà quindi l'illusione che il metodo generato sia stato chiamato direttamente.

Svantaggi di AUTOLOAD

AUTOLOAD() può essere uno strumento utile, ma è difficile da usare correttamente. Un approccio naïf alla generazione di metodi a tempo di esecuzione significa tra l'altro che il metodo can() non è in grado di restituire un'informazione corretta sulle capacità degli oggetti e delle classi. La soluzione più semplice consiste nel predichiarare con la direttiva subs tutte le funzioni che pensate di gestire con AUTOLOAD() :

    use subs qw( rosso verde blu ocra grigio );

Ora lo Potete Vedere

Le dichiarazioni anticipate sono utili solo nei due casi piuttosto rari dell'uso degli attributi e dell'autoload (AUTOLOAD).

Questa tecnica ha il vantaggio di documentare le vostre intenzioni ma per contro vi obbliga a mantenere una lista statica di funzioni e metodi. A volte è meglio fare l'override di can():

    sub can
    {
        my ($self, $metodo) = @_;

        # usa il risultato del can() della classe padre
        my $rif_a_met = $self->SUPER::can( $metodo );
        return $rif_a_met if $rif_a_met;

        # aggiungi qui qualche filtro
        return unless $self->devo_generare( $metodo );

        $rif_a_met = sub { ... };
        no strict 'refs';
        return *{ $metodo } = $rif_a_met;
    }

    sub AUTOLOAD
    {
        my ($self) = @_;
        my ($nome) = our $AUTOLOAD =~ /::(\w+)$/;>

        return unless my $rif_a_met = $self->can( $nome );
        goto &$rif_a_met;
    }

AUTOLOAD() è come un bulldozer; può catturare funzioni e metodi che non avevate alcuna intenzione di gestire, come DESTROY(), il distruttore degli oggetti. Se scrivete un metodo DESTROY() senza implementazione, Perl non avrà problemi a invocarlo al posto di chiamare AUTOLOAD():

    # evita AUTOLOAD()
    sub DESTROY {}

Un Metodo Molto Speciale

I metodi speciali import(), unimport() e VERSION() non passano mai attraverso AUTOLOAD().

Se mescolate funzioni e metodi in un unico namespace che eredita da un altro package, e questo package a sua volta fornisce AUTOLOAD(), potreste vedere questo strano messaggio di errore:

  Use of inherited AUTOLOAD for non-method
      sbatti_porta() is deprecated

Se vi dovesse succedere, provate a semplificare il vostro codice; avete chiamato una funzione che non esiste in un package che eredita da una classe che contiene un proprio AUTOLOAD(). Ci sono diversi problemi: mescolare funzioni e metodi nello stesso namespace è spesso indice di un errore di progettazione; l'ereditarietà e AUTOLOAD() portano a un aumento molto rapido della complessità; e, infine, ragionare su del codice quando non sapete quali metodi vengono forniti dagli oggetti è difficile.

AUTOLOAD() è utile per programmi piccoli e scritti velocemente, ma è meglio evitarlo per avere del codice robusto.