Programmazione Funzionale in Java

Rispetto al paradigma di programmazione imperativo, in cui l’accento è posto sulla sequenza di comandi da eseguire, nella programmazione funzionale il programma assume la forma di una sequenza di operazioni matematiche mentre l’esecuzione corrisponde alla loro valutazione. Un aspetto fondamentale del paradigma funzionale è che richiede allo sviluppatore di dichiarare ciò che si vuole ottenere piuttosto che descrivere come ottenerlo.

Ad esempio, se volessimo trovare uno specifico valore all’interno di una lista, con la programmazione imperativa avremmo dovuto:

  • creare un ciclo for per scandire la lista;
  • estrarre l’i-esimo valore;
  • eseguire il test;
  • eventualmente terminare l’esecuzione.

Con il paradigma funzionale è possibile semplicemente dichiarare di eseguire un determinato test (funzione) su tutti gli elementi della lista, lasciando al compilatore i dettagli relativi a come scorrere la lista. Il codice funzionale è quindi più conciso e, poiché molti aspetti sono demandati al compilatore, risulta anche maggiormente esente da bug e più semplice da testare ed ottimizzare.

Tale paradigma, inoltre, non prevede side-effect in quanto le variabili vengono create esclusivamente al momento dell’esecuzione della funzione e sono distrutte subito dopo.

La programmazione funzionale è stata introdotta in java dalla versione 7 e poi migliorata nella 8.

Lambda Expression

Le espressioni lambda sono mutuate dal Lambda Calcolo ed in programmazione si riferiscono a funzioni anonime, ovvero che non sono associate ad alcun identificatore. La particolarità di queste funzioni è che possono essere definite una volta, assegnate ad un oggetto ed essere riutilizzate ogni volta che se ne ha bisogno senza possibilità che vi sia interazione (side effect) tra le varie esecuzioni.

In java era già possibile definire classi e metodi anonimi (vedi Classi anonime in java) ma non vi era la possibilità di poterle manipolare, ad esempio passando la funzione ad un’altra funzione. Per supportare le funzioni anonime in java è stata introdotta l’interfaccia  java.util.Function<T,R> che può essere utilizzata per identificare una funzione che ha un argomento di tipo T e restituisce un valore di tipo R. Questo implica che una stessa funzione lambda può essere applicata a variabili di tipo differente. Ad esempio l’espressione lambda:

x -> x + 1 

che esegue l’operatore + tra l’input e la costante 1, può essere definita in java come:

nel primo caso eseguirà l’incremento della variabile intera di input, mentre nel secondo la concatenazione con la stringa 1.

L’interfaccia Function dichiara un metodo apply() che consente eseguire il metodo anonimo sull’input specificato. Applicato al nostro esempio avremo:

Il primo produce il valore 2, mentre il secondo la stringa 0 + 1 = 1.

Funzioni N-arie

In alternativa all’interfaccia Function, in java 8 è definita anche l’interfaccia java.util.BiFunction<T, U, R> che consente di definire una funzione anonima che ha come argomenti due parametri di tipo T ed U. Ad esempio la somma tra due interi può essere definita attraverso la seguente funzione:

dove i parametri di input sono racchiusi tra parentesi tonde. L’esempio riportato è inoltre un caso semplice di operatore binario, ovvero di operatore che riceve in input due parametri (operandi) dello stesso tipo per restituire un valore del tipo uguale a quello dei parametri. Per questo particolare tipo di operatore java fornisce l’interfaccia BinaryOperator<T> che sostanzialmente estende BiFunction<T,T,T>. L’esempio visto sopra è quindi analogo a:

Un caso particolare è invece quello in cui l’operazione è unaria ed il dominio e codominio coincidono (oggetti dello stesso tipo). In questo caso java definisce l’interfaccia UnaryOperator<T> che invece estende Function<T, T>.

Non esiste però una interfaccia che consente di generalizzare definendo funzioni con tre o più parametri, semplicemente perché una funzione N-aria può essere dichiarata attraverso la concatenazione di funzioni. Ad esempio la somma di tre interi può essere dichiarata ed applicata ai dati di input nel modo seguente:

Method Reference

Abbiamo detto che le espressioni lambda consentono di definire funzioni anonime, ma possono anche identificare metodi esistenti in classi java. Il riferimento a tali metodi avviene utilizzando l’operatore :: in combinazione col nome della classe di appartenenza oppure un oggetto specifico e del metodo identificato.

Static Method Reference

Questo tipo di riferimento si ottiene concatenando il nome di una classe con un metodo statico in esso definito, secondo l’espressione:

che corrisponde a:

Ad esempio:

che identifica il metodo abs() della classe Math. Si tratta del metodo più semplice anche perchè sicuramente il più intuitivo.

Instance Method Reference a Oggetto Esistente

In questo caso il riferimento è ad un metodo di un oggetto esistente (instanziato), secondo l’espressione:

che corrisponde alla seguente espressione lambda:

in cui l’oggetto è un input dell’espressione insieme al parametro che viene applicato al metodo referenziato. Per comprendere meglio consideriamo il seguente esempio:

dove la funzione lambda instanceMethodReference identifica il metodo charAt della classe String ma applicato alla string "test string". Quindi l’istruzione instanceMethodReference.apply(i) restituirà il carattere alla i-esima posizione della stringa indicata.

Instance Method Reference a Oggetto di Tipo Specifico

Molto simile al caso precedente, l’espressione che lo caratterizza è:

che corrisponde alla seguente espressione lambda:

La differenza rispetto caso precedente è che l’oggetto su cui applicare il metodo è un input della funzione lambda. L’esempio precedente in questo caso diviene:

dove è stato necessario utilizzare la classe BiFunction per poter indicare sia alla classe String dell’oggetto che il parametro Integer di input alla funzione charAt(). Quindi l’istruzione l’esecuzione per restituire l’i-esimo carattere di una stringa diviene typeMethodReference.apply( "test string", i ).

Constructor Method Reference

In questo caso il riferimento è al metodo costruttore di una specifica classe identificandolo con l’operatore new, secondo l’espressione:

che corrisponde all’espressione lambda:

Il codice java per utilizzare tale tipologia di riferimento è:

mentre l’espressione constructoreMethodReference.apply( "test string" ) restituisce la stringa ottenuta invocando il costruttore della classe String che accetta una stringa come parametro.

Composizione di Funzioni

La composizione di funzioni è l’applicazione di una funzione al risultato di un’altra. Lo scopo è quello di ottenere nuove funzioni dalla composizione di funzioni base. Unico vincolo è che gli operatori siano unari (un solo parametro di input) e che il dominio e codomino siano dello stesso tipo.

Procediamo considerando le due funzioni seguenti:

Comporre le due funzioni significa ottenere una nuova funzione unaria che dato un input esegue le due operazioni nell’ordine scelto per ottenere l’output. Tale composizione è generalizzabile con una espressione lambda del tipo:

che nel dettaglio significa che compose identifica una funzione che riceve in input due funzioni unarie f e g e restituisce come output la funzione x -> g.apply(f.apply(x)) che è appunto la composizione delle funzioni date. La nuova funzione così ottenuta può quindi essere utilizzata nel modo seguente:

Un secondo metodo di composizione di funzioni unarie è quello di utilizzare i metodi compose()andThen() definiti nell’interfaccia Function. Tali metodi consentono entrambi di comporre le funzioni ma in ordine differente:

  • compose() invoca la funzione su cui è chiamato per ultima e la funzione parametro per prima;
  • andThen() invoca la funzione su cui è chiamato per prima e la funzione parametro per ultima;

La stessa composizione realizzata sopra può quindi essere ottenuta con i metodi:

Codice Sorgente

Il codice sorgente per tutti gli esempi riportati è scaricabile qui Functional Programming Java.

How useful was this post?

Click on a star to rate it!

Average rating 5 / 5. Vote count: 2

No votes so far! Be the first to rate this post.

As you found this post useful...

Follow us on social media!