Articolo scritto da Alessio Saltarin il 09/08/2008
Pagina 1 di 2
Introduzione
La programmazione funzionale (FP) è un paradigma di programmazione alternativo a quelli tradizionali (la programmazione strutturale o imperativa e la programmazione a oggetti) inventato, col nome di "calcolo lambda", da
Alonso Church negli anni 30, ben prima cioè che si sapesse esattamente cosa fosse un computer.
Gli studi di Church servirono come base allo sviluppo del linguaggio di programmazione Lisp, e poi furono quasi del tutto abbandonati, poichè si affermò la programmazione imperativa dei computer, che generò il Basic e via via il Pascal, il C, e oggi i moderni C++ e Java.
L'idea base del
calcolo lambda è che un programma per computer può essere espresso, invece che con una serie di istruzioni imperative (fai questo, poi fai questo, poi fai quest'altro), con una serie di funzioni i cui parametri sono altrettante funzioni.
Un programma funzionale è normalmente composto da una funzione che prende in input un'altra funzione che prende in input un'altra funzione e così via.
Questo fa sì che mentre un programma tradizionale, scritto con un paradigma imperativo o a oggetti, è composto da una serie di comandi che agiscono su delle variabili il cui valore rappresenta lo "stato" del programma, nella programmazione funzionale il concetto stesso di variabile non esiste (non esiste il concetto di "stato") e l'esecuzione è affidata a una serie di funzioni di funzione che agiscono su costanti.
L'interesse per i linguaggi funzionali si è perso nel tempo per alcuni motivi, tra cui soprattutto la difficoltà di apprendimento (normalmente un uomo pensa a oggetti, piuttosto che a funzioni, a meno che non sia un matematico!) e la difficoltà a reperire interpreti o compilatori efficienti.
Ruby e la FP
Oggi questo interesse si è però risvegliato, perchè la programmazione funzionale porta con sè una conseguenza assai preziosa: non può per definizione dare luogo a bug a run-time. In altre parole: al momento della compilazione o della prima esecuzione o funziona o non funziona. Non può comportarsi in modi non prevedibili a priori (in un programma funzionale, infatti, non esiste il concetto di eccezione).
Così sono nati e stanno prosperando alcuni linguaggi funzionali (più o meno puri, quindi con più o meno supporto alla programmazione tradizionale) quali: ML/
OcaML,
Haskell,
F#.
Ruby
non è un linguaggio funzionale, ma adotta alcune tecniche di programmazione funzionale che ci possono aiutare a formulare algoritmi più sintetici, più potenti e più efficaci. Oltre che, solitamente, di più facile lettura.
Each e map
La prima caratteristica tipica dei linguaggi che supportano in qualche modo la FP, è quella di avere nella propria libreria standard degli iteratori funzionali. Ad esempio:
$elementi = [1,2,3,4,5]
$elementi.map {|elem| puts elem + 1}
Le funzioni "
map" e "
each" di Ruby non sono altro che FP applicata! Infatti sono funzioni che hanno come argomento altre funzioni. In Ruby, poi, i
blocks cioè quelle parti di codice racchiuse tra
{} o tra
do/end sono funzioni anonime costruite apposta per essere argomenti di altrettante funzioni.
Tali funzioni nella FP si chiamano
high-order functions, cioè funzioni che prendono in input altrettante funzioni.
In particolare "map" agisce come una funzione matematica classica e può essere letta così: per ogni
elemento della serie da uno a cinque, esegui la funzione: stampa il numero naturale successivo.
Cioè "mappa", ovvero "associa", ad ogni elemento di una collezione (in Ruby diremmo di un Array) una certa funzione definita nel blocco.
La stessa cosa naturalmente accadrebbe con:
$elementi.each {...}
Closures: Proc e lambda
Le
closures rappresentano un concetto simile a quello di
high-order function: definiscono sostanzialmente la capacità di una funzione di agire su variabili che vivono in un contesto estraneo a quello della funzione stessa (ad esempio variabili globali o variabili di altre funzioni).
In Ruby, posso scrivere una closure utilizzando le funzioni anonime che possono essere definite con le parole chiave
Proc.new oppure
lambda.
Vediamo un esempio:
def moltiplicazione(moltiplicatore)
return lambda {|n| n*moltiplicatore }
end
per3 = moltiplicazione(3)
puts per3.call(3) #=> 9
puts per3.call(per8.call(2)) #=> 48
In questo esempio non proprio lapalissiano io definisco una funzione "moltiplicazione". Cos'ha di strano questa funzione? Semplicemente che io qui non faccio uso di alcuna variabile!
Infatti, l'argomento moltiplicatore
non è una variabile: è semplicemente un placeholder per una costante o per un'altra funzione.
Ma, nella programmazione tradizionale, io avrei scritto:
def moltiplicazione(a,b)
return a*b
end
puts moltiplicazione(3,3)
Come si vede però, utilizzando una
closure io definisco un "operatore" che chiamo
per3, il quale definisce il comportamento di tutte le moltiplicazioni "x3". Poi chiamo questo operatore sul numero 3. Posso anche richiamarlo ricorsivamente! O chiamarlo, invece che su una costante, su un'altra funzione.
Nella programmazione tradizionale, invece, io sono costretto a definire a priori il numero di variabili coinvolte nell'operazione - così limitandomi alle moltiplicazioni tra due numeri - e, cosa più notevole, devo inserire un concetto di stato, allocando la memoria per due variabili che conterranno i valori da moltiplicare.