Persino un programmatore che ha sempre e solo utilizzato l'assembler come "linguaggio" di programmazione sa cosa sia una funzione (magari l'ha chiamata routine, ma cosa importa?).
La funzione e' il mezzo tramite il quale astrai le operazioni ripetitive di un programma. Ma aspetta, questo modo di vedere la funzione e' riduttivo.
Cio' che la funzione astrae e' la parte
interna del programma, la polpa: Se devi astrarre la buccia le funzioni classiche, quelle con un nome, che non possono essere passate come argomento, non ti aiutano piu'.
Quando le funzioni funzionano
Ammettiamo di avere il seguente programma in pseudo-codice:
function validateUserPass(user,pass) {
if (user.len < 5 || user.hasBadChars) return false;
if (pass.len < 5 || user.hasBadChars) return false;
return true;
}
La funzione controlla che user e pass siano valide (non piu' corte di 5 caratteri e non contengono caratteri "cattivi"). Ma utilizzando una funzione potremmo evitare di scrivere le stesse cose due volte (o molte di piu', immaginiamo che questo controllo venga utilizzato molte volte). Cosi' scriviamo:
function validateField(f) {
if (f.len < 5 || f.hasBadChars) return false;
return true;
}
function validateUserPass(user,pass) {
if (validateField(user) && validateField(pass)) return true;
return false;
}
Il concetto di campo valido e' stato spostato dalla funzione originale ad una separata. Ora per il programmatore il concetto di campo valido e' diventato
astratto, non si deve piu' curare di come funziona, lo usa e basta.
Come detto pero', abbiamo cambiato un pezzo di polpa del programma. Immaginate il programma come un albero che ha via via rami piu' sottili. In questo caso i rami piu' sottili si ripetevano, e abbiamo fatto in modo di astrarli invece che crearli ogni volta che vogliamo ricostruire l'albero. Ma a volte le cose vanno in maniera diversa. Sono i rami piu' grossi che sono uguali e vorremmo astrarre! la scorza di un algoritmo che e' sempre uguale, mentre invece quello che cambia sono i rami piu' sottili.
Un esempio classico di questo problema e'
la paginazione di dati in HTML.
In una applicazione web complessa e' necessario fare la paginazione di dati
innumerevoli volte. Il concetto di paginazione e' sempre uguale dal punto di vista della logica con cui funziona:
- Ottieni gli elementi in un dato intervallo
- Visualizzali uno dopo l'altro nella pagina
- Crea una barra di navigazione con il numero di pagine
Cio' che cambia da una paginazione all'altra non e' questo concetto esterno, ovvero questa scorza dell'algoritmo, questi rami grandi, ma quelli piu' sottili, ovvero: Quali elementi ottenere? Come vengono visualizzati? Se sono utenti devo fare una cosa, se sono elementi di una TODO list devo fare qualcosa di diverso, e ancora una cosa diversa se sono risultati di un motore di ricerca.
Eppure cio' che il programma fa e' sempre la stessa cosa in teoria. Ma magari a volte vi siete ritrovati ad avere nella stessa applicazione innumerevoli pezzi di programma che si assomigliano molto tra di loro perche' fanno la paginazione di questo o di quello.
La cura
Qualunque potente linugaggio di programmazione puo' risolvere questo problema, ma in questo articolo mi preme far notare come il polimorfismo (un concetto della programmazione orientata agli oggetti) e le funzioni come oggetti di prima classe (un concetto della programmazione funzionale) siano piu' o meno equivalenti nel modo in cui risolvono il problema, anche se ognuno di questi approcci ha meriti e demeriti.
Utilizzando la OOP per la paginazione si potrebbe scrivere la seguente funzione:
function listItems(start,items) {
itemsList = items.getRangeOfItems(start,start+items.perPage);
itemsCount = items.getNumerOfItems();
foreach(itemsList as item) {
items.RenderAsHtml(item);
}
....
/* codice per creare una barra di navigazione utilizzando il fatto
che conosciamo sia il numero di items per pagina (items.perPage) che
il numero totale di items (itemsCount). */
....
}
Basta passare a listItems un oggetto 'items' che esporta la stessa interfaccia (gli stessi metodi e attributi insomma) una volta per le news, una per gli utenti, una per altre cose ancora ed e' fatta. Possiamo creare liste di cose con la paginazione senza riscrivere ogni volta l'algoritmo.
Lo stesso codice puo' essere anche scritto in maniera funzionale. Cosa accade infatti se utilizzo un linguaggio in cui le funzioni possono accettare come argomenti altre funzioni? Posso passare i "metodi" come funzioni e il gioco e' fatto:
function listItems(start,perPage,getRange,getNum,renderAsHtml) {
itemsList = getRange(start,start+perPage);
itemsCount = getNum();
foreach(itemsList as item) {
RenderAsHtml(item);
}
....
/* codice per creare una barra di navigazione utilizzando il fatto
che conosciamo sia il numero di items per pagina (perPage) che
il numero totale di items (itemsCount). */
....
}
I due approcci sono nella sostanza molti simili.
In realta' l'esempio potrebbe essere di molto migliorato nella pratica. Ad esempio le funzioni che ritornano gli items e il numero totale di items potrebbero essere una sola che torna un array associativo con le due informazioni (o un oggetto con due attributi).
Inoltre un parametro "filtro" e' praticamente indispensabile nella pratica.
Il filtro sara' passato alla funzione che si occupa di ritornare gli elementi della lista e il loro numero, e puo' essere qualunque cosa e' adatta nel contesto di programmazione. Ad esempio un pezzo di SQL come "WHERE time < ..." o una funzione che si occupa di filtrare i contenuti che vogliamo visualizzare da quelli che non vogliamo che appaiano in un data lista. Il bello e' che non importa di preciso cosa sia il filtro dal punto di vista della funzione listItems(), che si occupa solo di passare il filtro alle funzioni che poi fanno il lavoro sporco.
Questo post e' gia' troppo lungo, cosi' nel prossimo mostrero' una implementazione reale in PHP di questo concetto, e affrontero' le differenze pratiche dei due approcci nel caso in cui il linguaggio utilizzato e' molto limitaito sia dal punto di vista dell'approccio funzionale che di quello OOP (PHP4 e' limitatissimo in tutti e due i casi).
Buon anno!