Higher-Order Perl, a review
This review has been originally published in July, 2005
Higher-Order Perl (HOP) non è un testo che si possa sfogliare come un cookbook: poiché ritengo che i concetti esposti siano nuovi per il programmatore Perl tipico credo che il libro vada letto, studiato e assimilato. Quello che dice Damian Conway nella sua breve presentazione dà una misura di quanto sia interessante:
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.
È 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 "di passaggio" a proposito di un certo brano di codice contenuto nel libro:
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 IRC #nordest.pm
1, 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 una 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.
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):
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".
Footnotes:
Il canale non esiste più e si chiama ora #perl.it
.