| |||
| © Perl Mongers Italia. Tutti i diritti riservati. | |||
Per chi non lo conoscesse, Damian Conway è uno dei punti di riferimento nella comunità Perl: moduli, libri, conferenze... Una delle sue ultime fatiche, Perl Best Practices, riporta una serie di 256 raccomandazioni per migliorare il proprio modo di produrre codice. Una cosa che ho trovato interessante nel libro è stata il fatto che, in moltissime occasioni, sembra una promozione dei propri moduli pubblicati su CPAN. Non so, m'è sembrato un po' autocelebrativo, ma se non si autocelebra Conway parlando di Perl... chi può farlo? In una raccomandazione, in particolare, suggerisce di utilizzare il modulo Contextual::Return, che permette di restituire, in uscita da una funzione, valori differenti a seconda del contesto in cui la funzione viene chiamata.
vucumpr*cough* wantarray?Come i valori restituiti dalle funzioni, anche Conway cerca di essere ``un uomo per tutte le stagioni'': il modulo, infatti, può essere utilizzato in vari modi a seconda del contesto (anche culturale) di chi lo usa. Uno degli scopi del modulo è quello di consentire di scrivere codice più leggibile. Perl, sostiene Conway, permette già di restituire valori differenti a seconda del contesto in cui viene chiamata una funzione; tali contesti sono sostanzialmente tre ``e mezzo'': void, scalare (che conta come uno e mezzo, come si può vedere) e lista: funzione(); # Contesto void my $valore = funzione(); # Contesto scalare print "ciao" if (funzione()); # Contesto scalare/booleano my @valori = funzione(); # Contesto lista Per distinguere i tre casi Perl mette a disposizione la funzione
Per quanto
sub funzione {
return qw( ciao a tutti ) if wantarray; # definito e vero
return 'scalare' if defined wantarray;
print "void!\n"; # E` avanzato solo il contesto scalare!
return;
}
Si sta chiedendo al lettore del nostro codice di avere sempre ben
chiara l'associazione fra i tre possibili valori di Qui entra in gioco una prima incarnazione di use Contextual::Return;
funzione(); # Contesto void
my $valore = funzione(); # Contesto scalare
print "ciao" if (funzione()); # Contesto scalare/booleano
my @valori = funzione(); # Contesto lista
sub funzione {
return qw( ciao a tutti ) if LIST;
return 'scalare' if SCALAR;
print "void!\n" if VOID;
return;
}
Le classiche cose che uno dice "bastava pensarci!". Con la differenza
che poi si attende che ci pensi qualcun altro.
Zucchero sintatticoIo mi sarei fermato a questo punto, perché sono un tipo un po' grezzo che non sta troppo lì a rifinire. Conway, che invece il Perl lo sa davvero, è andato avanti e ci permette di scrivere una cosa del genere: use Contextual::Return;
sub funzione {
return (
LIST { return qw( ciao a tutti ); }
SCALAR { return 'scalar'; }
VOID { print "void!\n"; }
);
}
Molto d'effetto, non c'è che dire. Vi starete chiedendo: ma come fa questa
roba a compilare correttamente?!? Semplice, se diamo un'occhiata
al codice sorgente, ad esempio per la funzione
sub LIST (;&$) {
my ($block, $crv) = @_;
# ecc. ecc.
return $crv;
}
La funzione
LIST { return qw( ciao a tutti ); }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
questa e` una funzione anonima
Poiché stiamo specificando nel prototipo di LIST che ci aspettiamo
un riferimento ad una sub (è questo il significato della lettera
E lo scalare? Beh, prima di tutto è opzionale, per cui possiamo anche
non metterlo, come nella chiamata finale a
VOID { print "void!\n"; }
Il fatto che ciascuna funzione possa restituire uno scalare è invece
utile per le concatenazioni: il valore proveniente dalla chiamata a
Per me, che ho sempre vissuto in una località di mare, già con questo zucchero sintattico siamo arrivati sull'Everest. Conway, a questo punto, accende i razzi e punta dritto sulla Luna. Per il momento vediamo se e dove arriva, poi eventualmente lo seguiamo.
Valori scalari pigriMentre i blocchi relativi ai contesti lista e void sono valutati subito,
i blocchi relativi alla parte della funzione Come programmatori Perl, sicuramente esercitate quotidianamente la vostra pigrizia. Perché fare oggi qualcosa che si può rimandare a domani? O, meglio, non fare mai? No, non è un'esortazione a lasciare il letto come una cuccia perché, tanto, stasera lo disferete di nuovo; piuttosto è una riflessione su come sia inutile mettersi a cucinare se abbiamo una mezza idea di andare a mangiare fuori con gli amici. In molti casi, è bene ritardare il più possibile l'esecuzione di un calcolo - potrebbe darsi che alla fine non ci sia bisogno del risultato! Questo è quello che si dice avere un approccio pigro alla valutazione di una funzione. In pratica, quello che restituisce
sub make_counter {
my $counter = 0;
return SCALAR { $counter++ }
}
my $idx = make_counter();
print "$idx\n"; # Stampa 0
print "$idx\n"; # Stampa 1
print "$idx\n"; # Stampa 2
In pratica
C'è scalare e scalareSuperata l'atmosfera, Conway entra nella parte finale del viaggio sulla Luna. Il suo ragionamento suona più o meno così: a volte non abbiamo a disposizione abbastanza modi differenti di restituire un valore da una funzione. Come? Avete capito bene: Dunque: se il valore restituito viene utilizzato come numero devo fare una cosa; come stringa ne devo fare un'altra; in un test, ossia in un contesto booleano, un'altra ancora e così via. E viene fuori che possiamo scrivere mostri di questo tipo:
sub get_server_status {
my ($server_ID) = @_;
# Acquire server data somehow...
my %server_data = _ascertain_server_status($server_ID);
# Return different components of that data
# depending on call context...
return (
LIST { @server_data{ qw(name uptime load users) } }
BOOL { $server_data{uptime} > 0 }
NUM { $server_data{load} }
STR { "$server_data{name}: $server_data{uptime}" }
VOID { print "$server_data{load}\n" }
DEFAULT { croak q{Bad context! No biscuit!} }
);
}
Sono comparse nuove funzioni:
Come fa a funzionare? In fondo se dico:
my $valore = get_server_status('pinco');
la variabile
if ( my $status = get_server_status() ) { # True if uptime > 0
$load_distribution[$status]++; # Evaluates to load value
print "$status\n"; # Prints name: uptime
}
Lo stesso
Ma quanti sono questi contesti?Parecchi, parecchi. Conway ci rende la vita ``semplice'' con il seguente grafico:
DEFAULT
^
|
|--< VOID
|
`--< NONVOID
^
|
|--< VALUE
| ^
| |
| |--< SCALAR
| | ^
| | |
| | |--< BOOL
| | |
| | |--< NUM
| | |
| | `--< STR
| |
| `--< LIST
|
|
`--- REF
^
|
|--< ARRAYREF
|
|--< SCALARREF
|
|--< HASHREF
|
|--< CODEREF
|
|--< GLOBREF
|
`--< OBJREF
Tutte le etichette rappresentano un possibile differente contesto. Il
padre di tutti i possibili valori restituiti è DEFAULT, che in pratica
viene utilizzato quando neppure
sub funzione {
my @dati = qw( ciao a tutti );
return (
LIST { return @dati; } # Buon vecchio contesto lista
ARRAYREF { return \@dati; } # Se vogliamo un riferimento
DEFAULT { die 'spiacente!' } # Imprevisto?!?
);
}
my @array = funzione();
my $a_ref = funzione();
my @secondo_array = @{ $a_ref }; # Stiamo utilizzando $a_ref come
# riferimento ad array!
Questo ci può togliere dall'imbarazzo di progettare un'interfaccia che restituisca una lista potenzialmente lunga oppure un riferimento ad un array che contiene la lista - più efficiente ma anche più complicato da utilizzare.
Che succede se non dico niente?La mia professoressa di disegno alle superiori diceva sempre che ``Chi tace... sta zitto''. In questo caso, invece, Conway ci legge nella testa ed impone dei comportamenti di riserva nel caso non siamo abbastanza espliciti nel dire cosa vogliamo fare. Ad esempio, supponiamo di avere la seguente funzione:
sub funzione {
return (
LIST { return qw( ciao a tutti ); }
NUM { return 42; }
DEFAULT { die 'spiacente!'; }
);
}
e di avere il seguente utilizzo: my $valore = funzione(); # Contesto scalare => pigrizia! print "Il valore e` $valore\n"; # Uhmm... STR ci starebbe bene! Purtroppo
DEFAULT
^
|
|--< VOID
|
`--< NONVOID
^
|
|--< VALUE <..............
| ^ :
| | :
| |--< SCALAR <.....:..
| | ^ :
| | | :
| | |--< BOOL :
| | | :
| | |--< NUM <..:..
| | | : ^ :
| | | v : :
| | `--< STR <....:..
| | :
| `--< LIST :
| : ^ :
| : : :
`--- REF : : :
^ : : :
| v : :
|--< ARRAYREF :
| :
|--< SCALARREF ..........:
|
|--< HASHREF
|
|--< CODEREF
|
|--< GLOBREF
|
`--< OBJREF
Normalmente i fallback seguono i percorsi tratteggiati, altrimenti
vanno su nel percorso con le linee dritte. Complicato? Ad esempio,
nel nostro caso c'è una freccetta che collega Lo È interessante la relazione fra
sub funzione {
my @dati = qw( ciao a tutti );
return (
LIST { return @dati; } # Buon vecchio contesto lista
DEFAULT { die 'spiacente!' } # Imprevisto?!?
);
}
my @array = funzione();
my $a_ref = funzione();
my @secondo_array = @{ $a_ref }; # Stiamo utilizzando $a_ref come
# riferimento ad array!
my $morte_certa = $a_ref + 1; # va a finire su DEFAULT -> die!
Nonostante tutto, non sono convintoCredo che non si possa negare che Il rendere esplicito il test sul contesto ``vecchia maniera'', ossia
quello realmente implementato in Perl, è a mio avviso la cosa veramente
utile dentro questo modulo. Va ammesso che la funzione A mio modesto parere, però, gli aspetti utili di questo modulo finiscono
qui; in particolare, trovo piuttosto inquietante la pletora di
variazioni sul tema messe a disposizione per il caso scalare. In
``Perl Best Practices'', Conway delinea il seguente scenario di utilizzo
tipico: una funzione che normalmente restituisce un array con vari
elementi può essere utilizzata in contesto scalare, ed utenti differenti
possono aspettarsi di ricevere dati differenti. Nel suo esempio, in
particolare, la funzione potrebbe restituire parametri statistici di
una macchina quando chiamata in contesto lista, mentre potrebbe dare
``il più significativo'' quando chiamata in contesto scalare. La
definizione di ``più significativo'' cambia ovviamente da utente
potenziale ad altro utente, per cui Conway individua in
Qui risiede la vera debolezza di tutto questo sistema.
Stiamo concentrando in una sola funzione troppe varianti; cosa più grave,
non stiamo imponendo almeno di poter distinguere queste varianti in
maniera esplicita (ad esempio mediante il passaggio di parametri
modificatori opportuni) in fase di chiamata, ma in maniera del
tutto implicita in fase di utilizzo. Si applicano, cioè, tutte le
critiche che possono essere mosse all'utilizzo di Va infine riconosciuto a Conway che, nel raccomandare Concludendo? Personalmente credo che eviterò di utilizzare questo
modulo, riservandomi di includerlo solamente per rimpiazzare
Dove andare a cercareLa pagina su CPAN contenente i moduli di Damian Conway si trova
all'indirizzo http://search.cpan.org/~dconway/; per trovare il
modulo di questa recensione si può andare all'indirizzo
http://search.cpan.org/search
(già che ci siete, date anche un'occhiata a Il libro citato in questa recensione, ``Perl Best Practices'', è molto interessante ed anche piuttosto consistente (nonostante le critiche di questa recensione). Molti consigli possono apparire banali, si potrebbe rimanere delusi dal fatto che non si trova niente di veramente strano o inusuale, ma è un libro su come scrivere codice manutenibile (possibilmente anche da altri) e segue la filosofia del ``fare cose semplici, non fare cose 'troppo intelligenti'''. La stessa filosofia che viene tradita da questo modulo, secondo me. Il libro è edito da O'Reilly, e si può trovare un po' di materiale sul loro sito, in particolare su http://www.oreilly.com/catalog/perlbp/ (dove troverete anche un capitolo di esempio gratuito). Buona lettura! | |||