Questo post nasce dall’esigenza di spiegare, a chi non è un esperto del mestiere, che cosa sia un linguaggio di programmazione funzionale, e perché risulti così ostico alla maggior parte dei programmatori. In questo blog si è già cominciato da tempo a vedere qualche linguaggio funzionale: prima Lisp, adesso OClam, grazie al grandissimo socio! Però questi linguaggi funzionali rimangono un po’ misteriosi, e quindi diamoci una sguardo generale.
In particolare, mi rivolgo ad alcuni matematici e fisici che ci seguono di tanto in tanto: secondo me potrebbero apprezzare molto un linguaggio di programmazione funzionale, una volta capita l’idea che ci sta dietro. In effetti si dice spesso che i linguaggi di programmazione funzionale siano i più graditi ai matematici: vediamo se riesco a spiegare perché.
Categorie
Se seguite questo blog dall’inizio, avrete capito che esistono più linguaggi di programmazione di quanti ne possiate immaginare. Alcuni sono delle meteore, prodotti della ricerca e poco più. Altri hanno avuto un modesto successo in alcune nicchie applicative. Alcuni sono diventati diffusissimi e usatissimi, nonostante i loro difetti. Altri sono rimasti dominio di pochi nerd, nonostante i loro pregi. E naturalmente nessuno sa perché, ad esempio, un linguaggio come Java abbia avuto tutto quell’enorme successo.
Come orientarsi tra questa selva di linguaggi di programmazione? Naturalmente, si fanno le tassonomie, ovvero si cercano di classificare i linguaggi di programmazione rispetto alle loro caratteristiche. Una delle prime categorizzazioni è la distinzione tra linguaggi imperativi e linguaggi funzionali
Linguaggi imperativi (o procedurali)
I primi sono di gran lunga i più diffusi, contando fra i loro membri la maggior parte dei linguaggi cosidetti Object Oriented. Stiamo parlando di stelle del firmamento come C++, Java, Python, Objective C, etc. Ma anche i vecchi linguaggi come C, Fortran, Cobol, Pascal.
In un linguaggio procedurale l’enfasi è sui dati (variabili, strutture dati, oggetti) e sui modi per processarli (tramite procedure, funzioni, metodi). In pratica, in un linguaggio procedurale si parte da un insieme di dati, e si applicano una serie di “operazioni” da eseguire in sequenza che li elaborano per produrre il risultato finale. Le operazioni da eseguire vengono descritte come una serie di istruzioni, comandi, ordini che il processore deve eseguire in sequenza per ottenere il risultato.
Oggi il modo di pensare del 90% dei programmatori è fortemente orientato alla programmazione procedurale. Uno pensa un algoritmo come una sequenza di passi più o meno elementari, ognuno dei quali modifica i dati finché non si arriva al risultato finale.
Linguaggi funzionali
I linguaggi funzionali, invece, pongono tutta l’enfasi sulle funzioni. Non che non ci siano i dati: però le funzioni sono in qualche modo più importanti. Per esempio, in un linguaggio funzionale una funzione può essere argomento di un’altra funzione, oppure il suo valore di ritorno; data una funzione di due variabili, è possibile fare una proiezione su una delle due variabili (assegnandogli una valore) ottenendo una funzione di una variabile.
Inoltre, di solito nei linguaggi funzionali le funzioni sono pure. Ovvero, non hanno effetti nascosti. Il risultato di una funzione per certi valori degli argomenti sarà sempre lo stesso, indipendentemente da quando la funzione viene valutata (cioè in quale ordine nella sequenza di operazioni da effettuare).
Prima di continuare è bene fare degli esempietti molto semplici per capire di cosa stiamo parlando. Scriveremo gli esempi in Python (come esempio di linguaggio imperativo); e in Haskell (come esempio di linguaggio funzionale). Avvertenza per gli esperti: ovviamente Python ha delle caratteristiche “funzionali”, e “Haskell” qualche aspetto “imperativo”. Ma per fare gli esempi useremo Python sempre in modalità “imperativa”, e “Haskell” per lo più in modalità “funzionale”.
Esempio 1: Fattoriale
La funzione fattoriale in matematica è una funzione definita sui numeri naturali (interi positivi) ed è semplicemente data dal prodotto di tutti i numeri naturali fino all’argomento n. In altre parole: Image may be NSFW.
Clik here to view.. Se vogliamo scrivere un semplice programmino Python che calcola il fattoriale potremmo fare come nel listato seguente:
def fatt(n) : prod = 1 if n > 1 : for i in range(2,n+1) : prod = prod * i return prod def main : risultato = fatt(5) print risultato
Nella funzione fatt, se l’argomento è minore o uguale a 1, il risultato viene 1. Se invece l’argomento è maggiore di 1, allora facciamo un ciclo for (linee 4-5) in cui moltiplichiamo ripetutamente il risultato per il valore della variabile i che viene incementata da 2 a n.
In pratica, diciamo operativamente come calcolare il fattoriale, dando una serie di comandi (adesso metti prod a 1; quindi moltiplica prod per 2, poi per 3, … e infine per n; quindi ritorna il risultato in prod).
Nei linguaggi funzionali, invece, non esistono i cicli. Se proprio dovete ripetere delle operazioni, potete usare la ricorsione. Per esempio, la funzione fattoriale in Haskell è la seguente.
module Main where import System.Environment main :: IO() main = do args <- getArgs putStrLn("Factorial of " ++ (args !! 0) ++ " = " ++ show( fatt(read(args !! 0))) ) fatt :: Integer -> Integer fatt 1 = 1 fatt n = n * fatt(n-1)
Saltate per il momento tutta la prima parte, e concentratevi sulla funzione fatt che comincia alla linea 11. Innanzitutto, definiamo fatt come una funzione che va dal dominio degli interi al codominio degli interi. In notazione matematica si scriverebbe: Image may be NSFW.
Clik here to view.. Più o meno ci siamo.
Poi diciamo che fatt(1) fa 1. Il simbolo di uguaglianza qui significa “definito come”: si definisce che la funzione fatt, quando ha come ingresso il valore 1, ci da come risultato il valore 1. Nei linguaggi imperativi in cui la sintassi derivi dal linguaggio C, il simbolo di uguale significa “assegnamento”, ovvero un’operazione che assegna il valore dell’espressione a destra dell’uguale alla variabile a sinistra. Ma nei linguaggi funzionali non c’è alcun assegnamento: il simbolo di uguale recupera il suo significato matematico originale.
Infine, alla linea 13 diamo la definizione della funzione fatt nel caso generale: e diciamo che la funzione fatt per un qualunque intero n è definita come il prodotto di n per il valore della funzione fatt in (n-1).
L’ordine delle definizioni è importante in Haskell: la prima definizione alla linea 12 ha precedenza sulla definizione alla linea 13: quindi, per n=1 si preferisce la definzione alla linea 12, mentre nel caso generale si usa la definizione alle linea 13.
Per compilare il programma, salvatelo sul file fatt.hs. Poi usate il compilatore ghc così: ghc fatt.hs. Questo produrrà un file eseguibile fatt, che lancerete così:
$ ./fatt 5
che calcolerà il fattoriale di 5. Il significato delle linee di codice 1-10 è un bel po’ complicato, e magari lo vedremo un’altra volta. Fidatevi che fa quello che uno si aspetta.
Conclusioni parziali
Naturalmente, anche in Python si può usare la ricorsione, come con tutti i linguaggi imperativi. In effetti, l’esempio che vi ho portato oggi è molto ma molto semplice. Però, spero che abbiate cominciato a capire una piccolissima differenza: mentre con Python potete fare la ricorsione, oppure programmare in maniera imperativa, oppure mischiare entrambe le cose, Haskell vi forza a programmare con la ricorsione, e vi impedisce di fatto di utilizzare comandi imperativi. Non potete definire variabili locali temporanee nelle funzioni; non potete modificare variabili globali (che in realtà non esistono). Semplicemente dovete sforzarvi di ridurre tutti a delle funzioni. E non è affatto semplice!
(Continua…)
Archiviato in:Funzionali, Haskell, Linguaggi Image may be NSFW.
Clik here to view.
Clik here to view.
