-+  Documenti
 |-  Bibliografia
 |-  Articoli
 |-  Perlfunc
 |-  F.A.Q.
 |-  F.A.Q. iclp
-+  Eventi
-+  Contatti
-+  Blog
-+  Link



 

Versione stampabile.


Aldo Calpini
Filosofo fallito, programmatore per vocazione. Nato e cresciuto a Roma, ha deciso di andare per il mondo in cerca di fortuna. Non l'ha trovata, ma ora vive e lavora a Vienna e non si lamenta troppo spesso.

Parlare del futuro, per noi perlisti, è divenuta purtroppo una faccenda imbarazzante. Da talmente tanti anni ormai siamo convinti, o cercano di convincerci, che prima o poi il Perl6 arriverà, che abbiamo ineluttabilmente accettato il fatto che sarà il linguaggio di programmazione utilizzato dai nostri nipotini.

Ma oggi non sono qui per affliggervi, tutt'altro! Esiste già, se non altro, un effetto collaterale del Perl6. Ed è quello di aver spinto un sostanzioso numero di brillanti sviluppatori a spremersi le meningi per realizzare, in certa misura, una "compatibilità con le versioni successive" del linguaggo. In altre parole, aggiungere nuove e sempre più sorprendenti funzionalità al Perl5 attuale, in buona parte mutuate o ispirate da quello che sarà il Perl6.

I primi sforzi hanno portato al fiorire, su CPAN, di vari moduli nel namespace Perl6 (Perl6::Variables, Perl6::Rules, Perl6::Junction tanto per citarne alcuni). Utili a livello di prototipo per la progettazione del nuovo linguaggio, decisamente meno nell'uso quotidiano. Col passare del tempo, però, e con il consolidamento delle specifiche del Perl6, alcune nozioni - soprattutto per quanto riguarda la programmazione object oriented - sono "trapelate" in strumenti tutt'altro che puramente teorici. Mi riferisco principalmente a Moose, del quale potete leggere altrove su questo sito.

Questo dimostra sostanzialmente due cose: primo, che il processo di "invenzione" del Perl6 non è campato in aria, che le idee ci sono, sono buone e vale la pena di implementarle. Secondo, che il Perl5, nonostante gli anni, non ha ancora finito di stupirci per potenza, flessibilità e capacità di rinnovarsi.

L'oggetto del mio articolo è un modulo (due, in realtà) dall'apparenza innocua, che trovate su CPAN. Ai lettori più smaliziati non sfuggiranno i riferimenti al Perl6 impliciti nel modulo. Si chiama semplicemente autobox. Non ha un nome altisonante, non è composto da migliaia di righe di codice, eppure può davvero rivoluzionare il modo in cui scrivete codice Perl. Oggi.

"hello autobox"

La finalità del modulo autobox è enunciata quasi laconicamente nella sua documentazione: invocare metodi su tipi nativi.

In altre parole, tramutare qualunque termine di un programma in un oggetto. O meglio, visto che non si tratta di un vero e proprio tramutare, estendere la sintassi object oriented facendo sì che anche semplici valori, non solo ciò che esplicitamente instanziamo tramite un costruttore, diventino oggetto d'invocazione di un metodo.

Ma forse era più chiara la spiegazione laconica. Lasciamo da parte la teoria e vediamo un attimo come questa si traduce in pratica. Il modulo permette cose come questa:

    use autobox;
    my $greeting = "hello autobox"->upper();

Sorprendente, no? "hello autobox" è una stringa, eppure la riga sopra funziona (in realtà, no. Funzionerebbe se avessimo definito un metodo upper che converte una stringa in maiuscolo; ma ci arriviamo). In sostanza, il codice equivale ad aver scritto una cosa del genere:

    use String;
    my $string = String->new("hello autobox");
    my $greeting = $string->upper();

Supponendo l'esistenza di una classe String, dall'implementazione piuttosto ovvia. Eppure "hello autobox" non è un oggetto, non ci assomiglia neanche lontanamente. È una stringa!

Da un altro punto di vista, però, esso è anche uno scalare, ossia qualcosa che rappresenta un valore. Così come $foo (una variabile) o 2+2 (un'espressione). E autobox non fa altro che definire "classi virtuali", per così dire, delle quali implicitamente tutto può essere istanza. Nella fattispecie, la classe per la nostra stringa è SCALAR.

Dovremmo avere tutti gli elementi, a questo punto, per far effettivamente funzionare il nostro primo esempio:

    use autobox;
    sub SCALAR::upper { return uc(@_); }
    
    my $greeting = "hello autobox"->upper();
    # $greeting ora contiene "HELLO AUTOBOX"!

In pratica, basta definire metodi appartenenti alla "classe virtuale" e la loro invocazione avviene come se il termine fosse un'istanza di quella classe. Focalizzate i primi due esempi, quello che usa autobox e quello che usa l'ipotetica classe String. Questo è, in sostanza, quello che succede:

    "hello autobox"->upper()  corrisponde a: SCALAR::upper("hello autobox")
    $string->upper()          corrisponde a: String::upper($string)

La dicitura "tipi nativi", inoltre, non ci limita solo alle stringhe. Anche array e hash godono delle stesse prerogative. Un esempio:

    use autobox;

    sub ARRAY::sum {
        my $array = shift;
        my $sum = 0; 
        $sum += $_ foreach(@$array); 
        return $sum; 
    }

    my @numeri = (1..10);
    print @numeri->sum(); # stampa 55

Chiaro come il sole, no? Ok, lo so cosa state pensando. Sicuramente un trucchetto interessante, meritevole di un'alzata di sopracciglia. Ma andiamo, a chi potrebbe venire in mente di usare seriamente una sintassi del genere? Sono pienamente d'accordo con voi. Ma aspettate, il bello deve ancora venire...

Facciamo 31

Di per sé, come abbiamo visto, autobox non fornisce nulla se non la possibilità di utilizzare sintassi object oriented su non-oggetti. Per convertire una stringa in maiuscolo ho dovuto scrivere una funzione (metodo) che utilizzasse la parola chiave uc. Bell'affare.

Esiste però un altro modulo su CPAN, chiamato autobox::Core, il quale applica le potenzialità di autobox nientepopodimeno che al core del Perl, ossia tutte (o quasi) le sue parole chiave.

Basta usare il modulo, e la magia si compie sotto i nostri occhi increduli:

    use autobox::Core;
    
    "hello autobox"->uc()->print(); # stampa HELLO AUTOBOX
    
    my @numeri = (1..10);
    @numeri->join(",")->print(); # stampa 1,2,3,4,5,6,7,8,9,10

Ora sì che la cosa si fa davvero interessante! Questi esempi funzionano, usando una sintassi che non sembra solo il capriccio di un abile programmatore con troppo tempo libero. Una sintassi appetibile, elegante, certo spiazzante per il perlista di lunga data, ma sensata. Dannatamente sensata. Una sintassi, per dirla con un acronimo prettamente informatico, deliziosamente DWIM (Do What I Mean).

Andiamo sul concreto

Non pretendo di illustrarvi le conseguenze ultime dell'utilizzo di autobox. Del resto, io stesso non ho ancora veramente "rivoluzionato" il modo in cui scrivo Perl.

Voglio darvi però qualche assaggio, codice alla mano, partendo da un caso concreto. Dove a mio avviso autobox brilla davvero, e allevia alcune "brutture" congenite alla sintassi Perl, è nella gestione di strutture dati complesse.

Avevo come compito quello di realizzare una sorta di procedura di backup, in pratica serializzare su file alcuni dati contenuti in un database. I dettagli della procedura, comunque, sono poco importanti.

Comincio il mio lavoro definendo una struttura nella quale andare a memorizzare tutto quello che mi serve:

    my $data = {
        objects => [],
        languages => [],
        # ...
    };

Fin qui niente di speciale. Ora devo popolare la struttura con i dati provenienti da una query:

    foreach my $object ( do_query() ) {
    	push(@{ $data->{objects} }, $object);
    }

Quant'è brutto quel push, eh? Eppure inevitabile. Almeno finché non c'era autobox::Core:

    use autobox::Core;
    foreach my $object ( do_query() ) {
        $data->{objects}->push( $object );
    }

Non so voi, ma a me così piace molto di più! Ora diciamo che in un punto successivo del mio codice devo nuovamente scorrere la lista degli oggetti recuperati per qualche motivo:

    ## prima
    foreach my $object (@{ $data->{objects} }) {
        do_stuff($object);
    }
    
    ## dopo
    foreach my $object ($data->{objects}->flatten) {
        do_stuff($object);
    }

Ok, c'è un flatten che forse non ci aspettavamo: $data->{objects} contiene un riferimento ad array, e dunque dobbiamo in ogni caso dereferenziarlo per ottenere la lista vera e propria. Meglio flatten, comunque, che un più criptico @{ ... }. Scagli la prima pietra chi non ha mai dimenticato una parentesi graffa con la sintassi di "prima"!

Ma non solo. Se Perl è il linguaggio del "c'è più di un modo per farlo", autobox non è da meno. Anche queste forme sono possibili (per quanto personalmente preferisca il caro, vecchio foreach):

    $data->{objects}->foreach(sub {
        do_stuff($_[0]);
    });

    ## ma anche...
    $data->{objects}->map(sub {
        do_stuff($_);
    });

E ora supponiamo di avere una struttura dati che contiene un elenco (strutturato a sua volta) di file per ogni linguaggio, una cosa del genere:

    my %files = (
        en => {
            templates => 'po/templates_en.po',
            forms => 'po/forms_en.po',
        },
        it => {
            templates => 'po/templates_it.po',
            forms => 'po/forms_it.po',
            cities => 'po/cities_it.po',
        },
        # etc. etc.
    );

Devo andare a memorizzare in $data->{languages} l'elenco dei file che mi servono, ossia quelli dei linguaggi utilizzati negli oggetti che ho recuperato.

    ## prima
    my %language_seen;
    foreach my $object (@{ $data->{objects} }) {
        $language_seen{ $object->language_id } = 1;
    }
    foreach my $language (keys %language_seen) {
        push(@{ $data->{languages} }, values %{ $files{$language} });
    }	

La probabilità di scordarsi qualche parentesi cresce esponenzialmente... e ora vediamo cosa succede con autobox. Ma se dobbiamo farlo, facciamolo bene! Abbiamo già visto come sia facile definire metodi che vadano ad agire sui "tipi nativi", quindi chiamiamo in causa List::MoreUtils:

    ## dopo
    use autobox;
    use autobox::Core;
    use List::MoreUtils;
    sub ARRAY::uniq { return List::MoreUtils::uniq(@_) }

    foreach my $language ($data->{objects}->map( sub { $_->language_id } )->uniq->flatten) {
        $data->{languages}->push( $files{$language}->values->flatten );
    }

D'accordo, questo comincia a sembrare più un esercizio di stile che del serio e onesto codice. Del resto autobox è uno strumento, e in quanto tale soggetto ad essere abusato. Quello che ancora disturba un po', a mio avviso, è la necessità di introdurre con sub l'argomento del metodo map. Ma nessuno è perfetto :-).

Concludo con un esempio "da manuale". Tutti sappiamo cos'è la trasformata di Schwartz, ovviamente (altrimenti, date una ripassata). Questo codice, liberamente tratto dalla documentazione Perl, ordina un array secondo il primo numero presente nella stringa. Per capirci:

    my @data = ( 
        "Biancaneve e i 7 nani",
        "Ali baba e i 40 ladroni", 
        "I 3 Porcellini",
    );

    my @sorted = map  { $_->[0] }
                 sort { $a->[1] <=> $b->[1] }
                 map  { [ $_, (/(\d+)/)[0] ] } @data;

    # @sorted:
    # I 3 Porcellini
    # Biancaneve e i 7 nani
    # Ali baba e i 40 ladroni

    # (ossia 3, 7, 40)

Vediamo che faccia ha la nostra trasformata usando autobox:

    my @sorted = @data
        ->map ( sub { [ $_, (/(\d+)/)[0] ] } )
        ->sort( sub { my($a, $b) = @_; $a->[1] <=> $b->[1] } )
        ->map ( sub { $_->[0] } )
        ->flatten;

Più comprensibile? Meno comprensibile? Lascio a voi decidere. Sintassi a parte, la differenza sta nella sequenza delle istruzioni, che con autobox seguono il "flusso" naturale dei dati. La normale trasformata di Schwartz al contrario si legge, per così dire, da destra a sinistra:

    normale: map <- sort <- map <- array
    autobox: array -> map -> sort -> map

Nella documentazione di autobox::Core trovate alcune interessanti osservazioni sul perchè questa differenza è cosa buona e giusta, oltre naturalmente a vari altri illuminanti esempi d'utilizzo.

Conclusioni

Questo è un articolo breve (un pregio, direte voi): ho probabilmente tralasciato molti aspetti di autobox che meriterebbero di essere approfonditi.

Un avvertimento però è doveroso: l'utilizzo di autobox influisce sulle prestazioni! Non ho fatto personalmente benchmark, ma sicuramente le chiamate a metodi sono costose. Soprattutto se applicate a strutture dati molto corpose (ad esempio, array con svariate decine di migliaia di elementi).

Quello che apprezzo di più di autobox è la discrezione. Quel che aggiunge al linguaggio non è poca cosa: sarà simulazione quanto volete, ma di fatto il modulo regala al Perl ciò di cui moltri altri linguaggi - dinamici e non - si vantano: everything is an object.

E nonostante questo, non siete assolutamente forzati a utilizzare le parole chiave del Perl come fossero metodi. Anzi, potete scegliere caso per caso: nello stesso programma possono convivere pacificamente costrutti tradizionali e spericolati viaggi ai confini della sintassi. autobox non pesta i piedi a nessuno, e lo amo per questo.

Non so dire se autobox sia un gran passo avanti per l'umanità o un piccolo passo per il Perl (che lo avvicina - idealmente - al Perl6). Come ho già accennato, non l'ho utilizzato in maniera estensiva, ma quel poco che ho visto mi ha divertito molto e volevo condividerlo. Spero lo troviate divertente anche voi :-)


Ti è piaciuto questo articolo? Iscriviti al feed!











Devo ricordare i dati personali?






D:
Annunci Google