Programmazione funzionale

La programmazione funzionale è un paradigma su cui la programmazione si basa quasi interamente sulle funzioni, estendendo il concetto di funzione secondo la sua definizione matematica, e non come dei semplici sottoprogrammi dei linguaggi imperativi come abbiamo visto finora.
Nei linguaggi funzionali puri un programma consiste esclusivamente nell’applicazione di diverse funzioni a un valore di ingresso per ottenere un valore di uscita.
Python, senza essere un linguaggio puramente funzionale include diverse funzionalità prese in prestito dai linguaggi funzionali, quali le funzioni di ordine superiore o le funzioni anonime (lambda).
Funzioni di ordine superiore
Il concetto di funzioni di ordine superiore si riferisce all’utilizzo di funzioni come se fosse un valore, consentendo di passare funzioni come parametri di altre funzioni o restituire funzioni come valore di ritorno.
Ciò è possibile perché, come abbiamo sottolineato in diverse occasioni, in Python tutto è oggetto. E le funzioni non fanno eccezione.
Ecco un piccolo esempio
			def salutare(lang):
			    def salutare_it():
			        print "Ciao"
			    def salutare_es():
			        print "Hola"
			    def salutare_en():
			        print "Hi"
			    def salutare_fr():
			        print "Salut"
			
			    lang_func = {"it": salutare_it,
			    			 "es": salutare_es,
			                 "en": salutare_en,
			                 "fr": salutare_fr}
			    return lang_func[lang]
			    
			f = salutare("it")
			f()
Come possiamo vedere, la prima cosa che facciamo nel nostro piccolo programma è chiamare la funzione salutare con il parametro “it”. Nella funzione salutare si definiscono varie funzioni: salutare_es, salutare_it, salutare_en e salutare_fr; successivamente si crea un dizionario le cui chiavi sono stringhe di testo che identificano ogni linguaggio e come valori i nomi delle funzioni. Il valore di ritorno della funzione è una di queste funzioni. La funzione di ritorno è determinata dal valore del parametro lang che è stato passato come argomento di salutare.
Come valore di ritorno salutare è una funzione, come abbiamo visto, questo significa che f è una variabile che contiene una funzione. Possiamo quindi chiamare la funzione riferita da f come chiameremmo qualsiasi altra funzione, con l’aggiunta delle parentesi tonde e, facoltativamente, una serie di parametri tra le parentesi.
Questo potrebbe essere ridotto, visto che non è necessario memorizzare la funzione che ci passa come valore di ritorno in una variabile per poi chiamarla:
			>>> salutare("en")()
			Hi
			>>> salutare("fr")()
			Salut
In questo caso, la prima coppia di parentesi indica i parametri della funzione salutare e la seconda coppia, la funzione restituita da salutare.
Iterazioni di ordine superiore sulle liste
Una delle cose più interessanti che possiamo fare con le nostre funzioni di ordine superiore è quello di passarle come argomenti delle funzioni map, filter e reduce. Queste funzioni ci permettono di sostituire i cicli tipici dei linguaggi imperativi utilizzando costruzioni equivalenti.
map(function, sequence[, sequence, ...])
La funzione map applica una funzione ad ogni elemento di una sequenza e restituisce una lista con il risultato dell’applicazione della funzione su ogni elemento. Se passate come parametri n sequenze, la funzione accetterà n argomenti. Se una delle sequenze è più piccola delle altre, il valore che arriva alla funzione function per posizioni maggiori della dimensione di questa sequenza sarà None.
Qui possiamo vedere un esempio dove viene utilizzato map per fare il quadrato tutti gli elementi di una lista:
				def quadrato(n):
				    return n ** 2
				
				l = [1, 2, 3]
				l2 = map(quadrato, l)
filter(function, sequence)
La funzione filter verifica che gli elementi di una sequenza soddisfino una certa condizione, restituendo una sequenza con gli elementi che compiono tale condizione. Cioè, per ogni elemento di sequence si applica la funzione function; se il risultato è True viene aggiunto alla lista altrimenti si scarta.
Qui vediamo un esempio in cui viene utilizzato filter per conservare i numeri pari.
				def e_pari(n):
				    return (n % 2.0 == 0)
				
				l = [1, 2, 3]
				l2 = filter(e_pari, l)
reduce(function, sequence[, initial])
La funzione reduce applica una funzione a coppie di elementi di una sequenza fino a lasciarla in un singolo valore.
Qui si può vedere un esempio che viene utilizzato reduce per sommare tutti gli elementi di una lista.
				def somma(x, y):
				    return x + y
				
				l = [1, 2, 3]
				l2 = reduce(somma, l)
Funzioni lambda
L’operatore lambda viene utilizzato per creare funzioni anonime online. Essendo funzioni anonime, cioè senza nome, non è possibile fare riferimento in seguito.
Le funzioni lambda sono costruiti tramite l’operatore lambda, i parametri della funzione separati da virgole (attenzione, senza parentesi), due punti (:) e il codice della funzione.
Questa costruzione avrebbe potuto essere utile negli esempi precedenti per ridurre il codice. Il programma che utilizziamo per spiegare filter, per esempio, potrebbe essere espresso così:
			l = [1, 2, 3]
			l2 = filter(lambda n: n % 2.0 == 0, l)
Confronto che con la versione precedente:
			def e_pari(n):
			    return (n % 2.0 == 0)
			    
			l = [1, 2, 3]
			l2 = filter(e_pari, l)
Le funzioni lambda sono limitate da una sintassi a singola espressione.
Comprensioni liste
In Python 3000 map, filter e reduce perderanno importanza. E anche se queste funzioni rimarranno, reduce passerà a formare parte del modulo functools, per cui sarà fuori delle funzioni disponibili per impostazione predefinita; map e filter si sconsiglieranno a favore delle list comprehension o la comprensione delle liste.
La comprensione di liste è una caratteristica presa in prestito dal linguaggio di programmazione funzionale Haskell che è presente in Python dalla versione 2.0 ed è una costruzione che permette di creare liste da altre liste.
Ciascuna di questa costruzioni è costituita da un’espressione che determina come modificare l’elemento della lista originale, seguita da una o più clausole for e facoltativamente una o più clausole if.
Ecco un esempio di come si potrebbe usare una list comprehension per fare il quadrato di tutti gli elementi di una lista, come abbiamo fatto nel nostro esempio di map.
			l2 = [n ** 2 for n in l]
Questa espressione viene letta come “per ogni n in l fare n ** 2“. Come vediamo abbiamo anzitutto l’espressione che modifica i valori della lista originale (n ** 2), poi il for, il nome che useremo come riferimento all’elemento corrente della lista originale, lo in, e la lista su cui viene iterato.
L’esempio che abbiamo usato per la funzione filter (conservare solo i numeri pari) potrebbe essere espressa con una list comprehension come segue:
			l2 = [n for n in l if n % 2.0 == 0]
Vediamo infine un esempio di list comprehension con varie clausole for:
			l = [0, 1, 2, 3]
			m = ["a", "b"]
			n = [s * v for s in m
			           for v in l
			           if v > 0]
			n
            ['a', 'aa', 'aaa', 'b', 'bb', 'bbb']           
Questa costruzione è equivalente ad una serie di for-in annidati:
			l = [0, 1, 2, 3]
			m = ["a", "b"]
			n = []
			
			for s in m:
			    for v in l:
			        if v > 0:
			            n.append(s* v)
			n
            ['a', 'aa', 'aaa', 'b', 'bb', 'bbb'] 
Generatori
L’espressioni generatrici funzionano in modo simile alla list comprehension. Infatti la sintassi è esattamente la stessa, tranne che per le parentesi che utilizzano le tonde al posto delle quadre:
			l2 = (n ** 2 for n in l)
Le espressioni generatrici differiscono dalla list comprehension perché non ritorna una lista, ma un generatore.
			>>> l2 = [n ** 2 for n in l]
			>>> l2
			[0, 1, 4, 9]
			
			>>> l2 = (n ** 2 for n in l)
			>>> l2
			<generator object at 0×00E33210>
Un generatore è una classe particolare di funzione che genera valori sui quali iterare. Per restituire il seguente valore su cui iterare si utilizza la parola chiave yield al posto di return. Vediamo ad esempio un generatore che restituisce numeri de n a m con un salto s.
			def mio_generatore(n, m, s):
			    while(n <= m):
			        yield n
			        n += s
			>>> x = mio_generatore(0, 5, 1)
			>>> x
			<generator object mio_generatore at 0x0123EC38>
Il generatore può essere utilizzato ovunque sia necessario un oggetto iterabile. Ad esempio in un for-in:
			for n in mio_generatore(0, 5, 1):
			    print n
			    
			0
			1
			2
			3
			4
			5   
Dal momento che non si crea una lista in memoria, ma genera un singolo valore ogni volta che è necessario, in situazioni in cui non è necessario avere una lista completa, l’utilizzo di generatori può fare una grande differenza nell’impiego di risorse. In ogni caso è sempre possibile creare una lista da un generatore tramite la funzione list:
			>>> lista = list(mio_generatore(0, 5, 1))
			>>> lista
			[0, 1, 2, 3, 4, 5]
Decoratori
Un decoratore è solo una funzione che riceve una funzione come parametro e restituisce un’altra funzione come risultato. Ad esempio potremmo desiderare di aggiungere una funzionalità che visualizza o stampa il nome della funzione chiamata per scopi di debug:
			def mio_decoratore(funzione):
			    def nuova(*args):
			        print "Chiamata alla funzione", funzione.__name__
			        ritorno = funzione(*args)
			        return ritorno
			    return nuova
Come si vede il codice della funzione mio_decoratore non fa altro che creare una nuova funzione e la restituisce. Questa nuova funzione consente di stampare il nome della funzione che “decoriamo“, esegue il codice di questa funzione e restituisce il suo valore di ritorno. Ciò significa che se chiamiamo alla nuova funzione che restituisce mio_decoratore, il risultato sarebbe lo stesso che chiamare la funzione direttamente passata come parametro, tranne l’aggiunta che stampa il nome della funzione.
Supponiamo ad esempio una funzione imp che non fa altro che visualizzare sullo schermo la stringa passata come parametro.
			>>> imp("Ciao")
			Ciao
			>>> mio_decoratore(imp)("Ciao")
			Chiamata alla funzione imp
			Ciao
La sintassi per chiamare alla funzione che restituisce mio_decoratore non è chiara, ma se si studia da vicino si vede che non è complicato. In primo luogo si chiama la funzione che decora con la funzione da decorare: mio_decoratore(imp), e dopo aver ottenuto la funzione già decorata, la si può chiamare passando lo stesso parametro che è stato passato in precedenza: mio_decoratore(imp)("Ciao")
Ciò potrebbe essere espresso più chiaramente precedendo la definizione della funzione che vogliamo decorare con il segno @, seguito dal nome della funzione decoratrice:
			@mio_decoratore
			def imp(s):
			    print s
			    
			>>> imp('Ciao')
			Chiamata alla funzione imp
			Ciao			    
In questo modo ogni volta che si chiama si chiama a imp si starà chiamando effettivamente alla versione decorata. Python incorpora questa sintassi a partire dalla versione 2.4 in poi.
Se vogliamo applicare più di un decoratore sarebbe sufficiente aggiungere una nuova riga con il nuovo decoratore.
			@altro_decoratore
			@mio_decoratore
			def imp(s):
			    print s
È importante notare che i decoratori si eseguiranno dal basso verso l’alto. Cioè, in questo esempio si esegue prima mio_decoratore e poi altro_decoratore.


Similari
Overloading di metodi in Java
12% Java
Un metodo overload viene utilizzato per riutilizzare il nome di un metodo ma con argomenti diversi, opzionalmente con un differente tipo di ritorno. [expand title=”Regole per overload” startwrap=”” endwrap=”” excerpt=”⤽” s…
Modi di fare e di non fare in Python
11% Python
Questo documento può essere considerato un compagno del tutorial di Python. Viene illustrato come utilizzare Python, e quasi ancora più importante, come non usare Python. [expand title=”Costrutti del linguaggio che non dov…
redirect 301 usando mod_alias
9% Server
mod_alias è fondamentalmente la versione più semplice di mod_rewrite. Non può fare le cose che fa mod_rewrite, ad esempio modificare la stringa di query. Per eseguire reindirizzamenti nel server web Apache è possibile di u…
Installare Python e Django su Windows
8% Django
Quando ci riferiamo allo sviluppo web con Python, la prima cosa che viene in mente è usare un qualche framework. Il più famoso e utilizzato da tutti è il Django, ma non è l’unico. Ci sono Pylons, Grok, TurboGears e Zope: t…
Metodi magici e costanti predefinite in PHP
8% Php
PHP fornisce un insieme di costanti predefinite e metodi magici per i nostri programmi. A differenza delle normali costanti i quali si impostano con define(), il valore delle costanti predefinite o speciali dipendono da do…