mercoledì 22 febbraio 2017

for Perl else ?

Aggiungere un "else" ad un ciclo for?
Tutto nasce dall'esigenza di eseguire del codice qualora un ciclo non venga mai eseguito...ma andiamo con ordine.
I cicli for (e foreach) di Perl si sono sempre comportati come ci si aspetta (dwim): se non ci sono iterazioni da fare non viene fatto nulla. Può sembrare stupefacente, ma Java, ad esempio, non è stato capace di fare la stessa cosa quando nella versione 5 ha inserito i "foreach":
Consideriamo una lista "objects" di oggetti di qualche tipo, ebbene il seguente blocco Java solleva una NullPointerException se la lista è nulla

for ( Object item : objects ){ ... }

mentre lo stesso ciclo Perl semplicemente non fa nulla

for my $item ( @objects ) { ... }

Ma il problema della discussione non è sulla nullità della lista, quanto sul fatto che la lista sia vuota. Nel caso la lista di oggetti sia "empty" (e/o non definita in Perl), il ciclo for non viene mai eseguito. Questo porta gli sviluppatori a scrivere codice "di guardia" per iniettare codice da eseguire qualora la lista non contenga valori:

if ( objects == null || objects.isEmpty() ){
  System.out.println( "Nessun elemento" );
}
else
  for ( Object item : objects ) { ... }

che in Perl diventa ovviamente piu' compatto:

if ( ! @objects ){ say "Nessun elemento"; }
for my $item ( @objects ) { ... }

Tralasciando l'eleganza degli operatori postfissi, l'idea Perl è quella di testare la bontà della lista: se la lista non è valida viene eseguito un blocco di codice, e comunque a seguire sempre il ciclo (che in realtà non viene mai eseguito). In sostanza in Perl non c'è l'esigenza della guardia attorno al ciclo, e questo è quello a cui mi riferivo quando intendevo dire che il ciclo for in Perl è DWIM!

Tuttavia, il dover testare la bontà della lista è una fatica doppia: viene fatta (da Perl) nel ciclo iterativo, e nel "catch" implementato dallo sviluppatore. Siccome Perl sa esattamente se la lista è buona o meno, dovendo eseguire o no il ciclo, è possibile demandare a lui questo ragionamento?
Entra nella stanza For::Else.
For::Else è un filtro sorgente (quindi che riscrive un blocco di codice) che si occupa di mettere la guardia if..else attorno al ciclo iterativo. La sintassi che ne risulta è come la seguente:

for my $item ( @objects ){ ... }
else { say "Nessun elemento"; }


ossia, come il nome suggerisce, viene fuori un for..else.
Bello?
Brutto?
Utile?
Da qui si apre un dibattito agguerrito su chi vuole una funzionalità simile e chi la ritiene inutile. In generale, diversi programmatori Perl vorrebbero un "catch" per gli iteratori, ma l'argomento è molto controverso (si veda qui e qui).

Un momento di pausa: potrebbe unless fungere allo scopo? Ovviamente no, perché sempre di una guardia attorno ad una iterazione si tratterebbe:

unless( @objects ){ say "Nessun elemento"; }
else{ for my $item ( @objects ) { ... } }


Come è noto l'uso di un unless..else è molto poco leggibile, e in generale io penso che l'unless non postfisso sia sostanzialmente inutile.

Un chiarimento per chi proviene da Python: questo linguaggio prevede una versione for..else ma il suo utilizzo appare contorto anche agli amanti dei serpenti. Anzitutto l'uso abbastanza infelice delle parole (e questo vale anche per For::Else): "else", ovvero "altrimenti", indica qualcosa da fare al posto di un'altra cosa. Nel caso del for..else di Python la clausola "else" invece indica qualcosa da eseguire quando il ciclo for termina regolarmente: il ciclo non deve eseguire del tutto o non deve essere terminato in mezzo ad una iterazione (uso di break). Uhm...

Chiarito che Python usa un for..else semanticamente differente da quello di cui si sta parlando qui, si torni alle considerazioni riguardo Perl.

Io personalmente penso non sia una buona idea implementare la clausola else in un iteratore.
Dovendo programmare con differenti linguaggi, spesso molto piu' stupidi che Perl, mi trovo comunque costretto a ricordarmi di dover inserire delle guardie, e quindi non è per me uno sforzo insormontabile "pensare" il mio codice con un test e un caso dedicato a liste "non buone".
Ma questo ovviamente vale per me.
Tuttavia considero che un simile operatore possa confondere chi si avvicina a Perl, che già risulta abbastanza complesso (chi si ricorda che for esegue un aliasing e while no? chi si ricorda che i cicli while sono pigri e i for no?), e ne uscirebbe probabilmente appesantito da un idioma poco utilizzabile.
Anzi poco usato.
Eh si, perché questo è il problema: dove si utilizzerebbe questo caso? A me personalmente viene in mente solo nella reportistica. Un caso che io affronto di frequente è quello di popolare una tabella di visualizzazione (html, o in altro formato) con tuple che provengono da un database, e in questo caso il for si comporta bene: se non ci sono tuple la tabella non viene popolata. Ma l'utente si spaventa se invece che vedere la lista dei pagamenti che deve effettuare vede uno spazio bianco, meglio quindi stampargli una stringa che dica "non ti resta nulla da pagare", e qui scatta il codice di guardia. Ma a parte questi casi, ce ne sono altri in cui si potrebbe avere una utilità pratica del costrutto for..else? A me non ne vengono in mente, e comunque anche il caso d'uso che ho citato prima riguarda piu' che altro il templating che non l'esecuzione del codice stesso, e quindi se proprio vogliamo inserire il for..else pensiamolo per il motore di template.
E se proprio si vuole utilizzare una bruttura, come forse for..else risulta, perché non usare map?

map { my $item = $_; ... } @objects or say "Nessun elemento";

In Perl 6 la situazione infatti è leggermente migliore: il ciclo for si comporta molto similmente all'operatore map, ritornando la lista degli elementi che hanno iterato correttamente, e quindi, a pena di qualche parentesi in piu', si può implementare un else mediante un or e l'idioma di map di cui sopra:

( for @objects -> $item { ... } ) || say "Nessun elemento";

Ancora una volta però è bene considerare la sanità mentale di chi dovrà mettere le mani su questo pezzo di codice, e che probabilmente proveniendo da altri linguaggi, sarà abituato al ben piu' diffuso idioma della guardia.

Nessun commento: