Le funzioni delegato
Per finire descrivo un pattern di programmazione funzionale standard: cioè la definizione di una funzione che prende in input come argomento un'altra funzione.
Questo tipo di funzioni sono molto utili per risolvere alcuni problemi di programmazione "difficili".
Uno di questi è il seguente: disponiamo di un algoritmo per effettuare un calcolo complesso che richiede molto tempo. Pensiamo ad esempio alla cifratura di un documento, o alla compressione di un'immagine molto grande.
Vorremmo applicare l'algoritmo e informare l'utente del progresso nell'avanzamento delle operazioni.
Peccato che, per farlo, noi dovremmo modificare il codice dell'algoritmo, inserendovi, in ciascuna iterazione, un richiamo alla funzione che ha come scopo quello di mostrare l'avanzamento dell'esecuzione.
Non solo a volte semplicemente questo non è fattibile - perchè ad esempio l'algoritmo è implementato in librerie che non hanno accesso alle funzioni di interfaccia grafica - ma se anche lo fosse introdurrebbe un'inefficienza nell'algoritmo stesso.
Le funzioni delegato (in C rappresentate dai puntatori a funzione) sono in tutto e per tutto dei tipi (come gli interi o le stringhe) che però, quando valutati, eseguono una parte di codice estranea alla funzione.
Un esempio ci aiuterà a capire meglio:
$elementi = [1,2,3,4,5]
def feedback(valore_massimo)
Proc.new do |valore_corrente|
putc '['
1.upto(valore_corrente) { putc '*' }
valore_corrente.upto(valore_massimo-1) { putc '-' }
if (valore_massimo == valore_corrente)
puts '] Fatto!'
else
puts ']'
end
end
end
def contatore(feedback_function)
x = 0
$elementi.map do |elem|
x = x+1
sleep 1
feedback_function.call(x)
end
puts
end
contatore(feedback($elementi.length))
La funzione feedback di cui sopra è una generica funzione che stampa una progress bar e aggiorna il relativo avanzamento. L'ho implementata con una high-order function. (In un progetto reale si utilizzerebbe una libreria per interfacce grafiche o almeno per la gestione della stampa su console, qui semplicemente invece stampo degli asterischi...)
La funzione contatore è quella che ci interessa. Qui supponiamo avvenga un qualche calcolo complesso (simulato con un sospensore di thread: sleep). Essa accetta come argomento una funzione delegato.
Come vedete nell'ultima riga, io passo come argomento a contatore la funzione feedback inizializzata con il numero massimo di elementi che mi aspetto di processare: un po' come se impostassi il valore massimo di una progress bar.
Ciò che accade a run time è che, ad ogni step dell'iterazione, dopo sleep, viene richiamata una certa funzione esterna, nel nostro caso la "feedback", cui la funzione delegato "feedback_function" punta.
Questo ci permette di mantenere un'impostazione del tutto generale al nostro codice: contatore infatti potrà essere usato con qualsiasi funzione in grado di trasferire un feedback: ad esempio una progress bar su Windows, una finestra su Linux con KDE, oppure con dei caratteri colorati su console attraverso una libreria come ncurses.
Conclusioni
La realizzazione di un progetto complesso in Ruby può avvalersi efficacemente di tecniche prese a prestito dalla programmazione funzionale, come:
| Corso Ruby e Ruby On Rails Creare software ed applicazioni Web con Ruby e ROR. A partire da 49 €. |