Stefano Rodighiero
HOP: ripensare la programmazione
http://www.perl.it/documenti/articoli/2005/07/-alla-sospirata.html
© Perl Mongers Italia. Tutti i diritti riservati.

Alla sospirata pubblicazione di "Higher-Order Perl" (HOP, d'ora in poi) non è ancora seguita alcuna recensione. Tanto entusiasmo che sparisce così improvvisamente è strano.

Non conosco i motivi per cui gli altri possessori del libro non abbiano ancora scritto, ma provo ad avanzare qualche ipotesi, che sono poi le ragioni per cui io non ho scritto nulla.

HOP non è un testo che si possa sfogliare come un cookbook: ritengo che i concetti esposti siano nuovi per il programmatore Perl tipico, quindi il libro va letto, studiato, assimilato. Questo dà una misura di quanto sia interessante: come infatti dice Damian Conway nella sua breve presentazione:

As a programmer, your bookshelf is probably overflowing with books that did nothing to change the way you program... or think about programming. You're going to need a completely different shelf for this book.
(Se siete programmatori, probabilmente il vostro scaffale straripa di libri che non hanno avuto alcuna influenza sulla vostra maniera di programmare... o sulla maniera di pensare alla programmazione. Avrete bisogno di uno scaffale del tutto diverso per questo libro)

Poi sì, c'è il fatto che il genere della recensione mi è ostile, e questo ha avuto il suo peso per il mio ritardo, ma è un altro paio di maniche.

E` un libro da digerire, dicevo, ma non è un libro che porta a scrivere codice concettuoso o difficile: Dominus si è anzi impegnato per fare in modo che il codice prodotto con le tecniche da lui insegnate apparisse naturale. Come dice a proposito di un certo brano di codice "di passaggio":

Although this works well, it has one big defect: it appears to have required cleverness
(Benchè funzioni bene, ha un grande difetto: sembra aver richiesto furbizia)

Cercherò di avvalorare tutte queste considerazioni con un esempio concreto di applicazione delle tecniche spiegate nel libro.

Come al solito, si parte da un problema reale: moz, uno degli avventori abituali del canale #nordest.pm, sta scrivendo un CGI che tra le altre cose deve produrre dinamicamente una query SQL secondo uno schema di questo genere:

SELECT * FROM tabella
 WHERE field1 = $valore1 $bool_op1
       field2 = $valore2 $bool_op2
       field3 = $valore3

Se $bool_op1 fosse stato un valore fisso, allora la soluzione sarebbe stata quasi banale: si usa join(), dopo aver preparato la lista delle clausole (anche questo era un problema, ma non lo tratterò), e via. Purtroppo però join() non va bene, ne servirebbe uno tipo questo:

ejoin [ $bool_op1, $bool_op2 ]
    , @clausole;

Ovvero un join speciale che accetti come parametro anche la sequenza dei separatori da usare. Si tratta di un tipico problema che si può risolvere con la cassetta degli attrezzi che si acquisisce leggendo HOP. Arriviamoci per gradi.

Per prima cosa scriviamo myjoin(), equivalente a join(): servirà ad acquisire una tecnica fondamentale per procedere. Cosa fa join? Piglia un separatore, una lista di stringhe e restituisce una stringa unica costituita da tutte le stringhe concatenate e "inframmezzate" dal separatore.

Tutte le volte che da una lista di valori se ne vuole ottenere uno solo, è molto probabile che il compito possa essere espresso in termini della funzione reduce. Si tratta di un costrutto tipico dei linguaggi funzionali, che si aspetta una sequenza di valori e la funzione da applicare a questi ultimi per ottenere il risultato. Ad esempio, la sommatoria è un esempio di reduce: i numeri da sommare sono la sequenza di valori, e la funzione è la somma.

reduce-sum.png

max() o min() sono ulteriori esempi di funzioni esprimibili in questi termini. Anche join(), come dicevo, è pensabile in termini di reduce: basta usare la funzione che concatena i valori mettendoci in mezzo il separatore.

Non difficile implementare reduce in Perl (dopo aver letto HOP, o per meglio dire copiando da HOP: si trova a pagina 344). Eccola:

sub reduce 
{
    my $code = shift;
    my $val  = shift;
    for (@_) { $val = $code->( $val, $_ ) }
    return $val;
}

reduce accetta come primo parametro una reference a codice, e usa il primo dei restanti parametri come accumulatore per il risultato finale. Poi, finchè ci sono altri valori, viene chiamata la sub referenziata da $code, passandole l'accumulatore e il prossimo valore. Applichiamo subito reduce per fare una sommatoria:

$somma = reduce( sub { $_[0] + $_[1] }
               , (1 .. 5) );

E lui da bravo restituisce 15. Bene. Tutta questa manfrina però era per riscrivere join(). E` presto fatto:

sub myjoin
{
    my $sep = shift;
    reduce( sub { $_[0] . $sep . $_[1] }, @_ );
} 

Ci stiamo avvicinando alla soluzione al problema iniziale. E` sufficiente fare in modo che $sep non sia sempre lo stesso, ma provenga da una sequenza passata alla procedura. Per farlo, adopereremo un altro arnese della cassetta messa a disposizione da HOP: gli iteratori.

Un iteratore è un ente che è capace di restituire il prossimo elemento di una sequenza: esso quindi conosce la sequenza e la posizione raggiunta all'interno di quest'ultima. A richiesta, fornirà il prossimo elemento e aggiornerà la posizione. Come molte altre cose in HOP, gli iteratori possono essere implementati con una closure. Ecco uno dei primi esempi che compaiono nel libro (si trova a pagina 121):

sub upto
{
  my ($m, $n) = @_;
  return sub {
    return $m <= $n ? $m++ : undef;
  };
}
my $it = upto( 3, 5 );

Per chiedere i valori all'iteratore è sufficiente richiamare la subroutine restituita da upto():

print $it->(); # 3
print $it->(); # 4
print $it->(); # 5

HOP suggerisce (a pagina 123) un idioma alternativo per evidenziare sintatticamente che si sta lavorando con un iteratore:

sub Iterator(&) { return $_[0] }

In maniera tale da poter scrivere

sub upto
{
  my ($m, $n) = @_;
  return Iterator {
    return $m <= $n ? $m++ : undef;
  };
}

Non è fondamentale, ma ne faremo uso. Anzi, ne facciamo subito uso per scrivere una piccola sub che da una lista produce un iteratore per quella lista (anche in questo caso sto copiando, per la precisione da pagina 161):

sub list_iterator
{
    my @items = @_;
    return Iterator {
        return shift @items;
    }
}

Ora abbiamo tutti i pezzi per risolvere il problema:

sub ejoin 
{
    my $seps = shift;
    my $it = list_iterator( @$seps );
    reduce( sub { $_[0] . $it->() . $_[1] }, @_ );
}

Sono due linee di codice. Ancora più importante, il codice non riserva sorprese e dovrebbe risultare semplice, purchè recepiti i meccanismi della funzione reduce e dell'iterator.

HOP è bello (e ve ne consiglio l'acquisto, anche se sta per essere pubblicato online integralmente) proprio per questo: fornisce un vasto insieme di nuove idee da applicare ai propri programmi. Per renderli più semplici, non più "furbi".