4. FP
• Un linguaggio può favorire uno stile di programmazione rispetto ad un altro, ma nulla di
più.
• Alcuni problemi sono più facilmente risolvibili con un approccio rispetto che con un altro.
• Le nuove implementazioni in Java8 rendono più semplice scrivere parti usando un
approccio funzionale, ma era possibile anche prima (con librerie esterne come Guava).
• Javascript consente un approccio event driven così come un approccio funzionale.
• Javascript è un linguaggio non tipizzato (loosely typed) e questo lo rende estremamente
flessibile, quindi anche l’approccio funzionale è del tutto possibile.Tuttavia è abbastanza
buffo se si considera che i linguaggi pensati per un approccio funzionale hanno solitamente
un forte controllo sui tipi per fornire un aiuto nella rilevazione di eventuali errori.
4
5. FP INTIME
• Si basa sul Lambda Calculus, sviluppato nel 1930
• LISP è stato il primo linguaggio funzionale (1960)
• Negli anni ’70 è stato sviluppato ML (MetaLanguage) presso l’università di Edimburgo
5
[https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-1-1f15e387e536#.muqfaya41]
6. IN MATEMATICA: FUNZIONI
• In matematica, una funzione è una relazione tra due insiemi, chiamati dominio e codominio della
funzione, che associa ad ogni elemento del dominio uno e un solo elemento del codominio.
• Se i due insiemi sono rispettivamente indicati con X eY, la relazione è indicata con f:X⟶Y e
l'elemento associato a x ∈ X tramite la funzione f viene abitualmente indicato con f(x) (si
pronuncia "effe di x”).
6
[Wikipedia]
7. COMPOSIZIONE DI FUNZIONI
In matematica, la composizione di funzioni è l'applicazione di una funzione al risultato di un'altra funzione.
Più precisamente, una funzione f tra due X eY trasforma ogni elemento di X in uno diY:
in presenza di un'altra funzione g che trasforma ogni elemento diY in un elemento di un altro insieme Z, si definisce la
composizione di f e g come la funzione che trasforma ogni elemento di X in uno diY usando prima f e poi g. Il
simbolo Unicode dell'operatore è ∘ (U+2218). (Wikipedia)
• Questo significa che si possono comporre solo funzioni il cui
codominio di una sia il dominio della successiva che verrà
applicata (quindi il controllo sui tipi aiuta!).
• Nei linguaggi di programmazione che consideriamo le
funzioni ritornano un solo valore (che ovviamente può
essere di qualsiasi tipo: numero, stringa, array, oggetto,
funzione…) Quindi ci troveremo a comporre funzioni con
un solo argomento come parametro.
• g∘f (x) = g(f(x)) quindi viene prima applicato f e poi g.
7
[Wikipedia]
8. FP:ALCUNI ASPETTI
• Le funzioni sono first class members
• Pure functions
• Immutabilità
• Function composition
• Currying
• Functor e Monad (either, optional, Java stream)
8
9. FIRST CLASS MEMBERS
• in un linguaggio funzionale, le funzioni sono un tipo di dati esattamente come gli altri (un numero, un testo). Quindi
possono essere passate come parametro ad un metodo o essere restituite come output di un altro.
• in Java8 questo è possibile, ma a volte è abbastanza prolisso (l’inferenza dei tipi ha ancora limiti, è necessario un uso esteso
dei generici, e c’è la necessità di dichiarare tipi se le interfacce funzionali già a disposizione non sono sufficienti)
• in JavaScript invece la cosa è del tutto normale
9
Unnecessary function wrapping:
in Javascript è totalmente
evitabile, in Java relativamente
alle possibilità offerte dal
linguaggio
[https://github.com/MostlyAdequate/mostly-adequate-guide]
10. PURE FUNCTIONS
“A pure function is a function that, given the same input, will always return the same
output and does not have any observable side effect. […]
A side effect is a change of system state or observable interaction with the outside
world that occurs during the calculation of a result.” (Mostly Adequate Guide to
Functional Programming)
Quindi una funzione che non dipende dallo stato di variabili esterne e che non altera i
dati (in JS ad esempio array.splice che modifica l’array vs array.slice che torna una
shallow copy modificata), non accede al file system o a un database…
Ovviamente, un software per poter funzionare, provocherà
inevitabilmente delle alterazioni all’esterno, l’obiettivo è relegare e
minimizzare le parti di codice impuro.
10
11. PURE FUNCTIONS WHY?
• Portable (self explanatory)
• Testable
• Referential transparency (un blocco di codice che può
essere sostituito con il suo valore valutato senza che
questo modifichi il comportamento del programma)
• Parallel code
11
12. IMMUTABILITÀ
• Nei linguaggi di programmazione puramente funzionali non esiste il concetto di
“variabile”, quando un valore viene assegnato ad un parametro x, questo rimane
immutabile per tutta la durata della vita di x.
• Quindi il concetto di loop non esiste in programmazione funzionale pura (su
cosa faccio il loop se non ho le variabili?!?).
• Ma, senza variabili? Si compongono funzioni. (in ogni caso nei linguaggi
funzionali si possono definire dei parametri per migliorare la leggibilità, solo che
una volta che gli è stato assegnato il valore, non è più possibile mutarlo)
• Ma, senza i loop? Dipende dal caso, o con la ricorsione o sfruttando le
funzionalità offerte in altro modo sulle collezioni di item.
12
13. CURRYING
13
Dicevamo che la composizione di funzioni si può fare solo su funzioni di un solo
parametro. E per funzioni che richiedono più parametri?
“In mathematics and computer science, currying is the technique of translating
the evaluation of a function that takes multiple arguments (or a tuple of arguments)
into evaluating a sequence of functions, each with a single argument.
Currying provides a way for working with functions that take
multiple arguments, and using them in frameworks where functions might
take only one argument” (Wikipedia)
Questo avviene in Javascript solitamente attraverso delle librerie aggiuntive.
Chiamando una funzione con meno argomenti di quelli che richiede, si ottiene una
nuova funzione che richiede gli elementi rimanenti.
Si chiama così in onore di Haskell Curry.
14. FUNCTOR
• Esiste una branca della matematica: Category theory, che cerca di unificare altre branche (l’idea di base è che
una categoria consiste in oggetti e funzioni). Chi ha sviluppato i primi linguaggi di programmazione funzionali si
è ispirato a concetti dalla teoria delle categorie. Un concetto interessante è quello del Functor.
• Per quanto ci riguarda l’idea è quella di avere un contenitore, in grado di contenere una proprietà. Quando la
proprietà viene messa nel contenitore, ci resta. Se il nostro contenitore si chiama C, il suo metodo of() crea un
contenitore C con all’interno il valore passato. Possiamo tirarla fuori, ma ha senso farlo solo in particolari
momenti. La sua utilità sta nel poter operare su C senza sapere necessariamente cosa c’è dentro.
• Una volta che la proprietà è nel contenitore, vogliamo applicare su di essa delle funzioni: ecco il metodo map().
• A Functor is a type that implements map and obey some laws
• E tanto basta, altrimenti ci si può avventurare nella CategoryTheory (per alcuni link vedi più avanti)
14
[https://github.com/MostlyAdequate/mostly-adequate-guide]
15. MONAD
• Allo stesso modo si può sentir parlare di monadi, per il momento ci
basta tenere presente questa definizione a grandi linee:
• “Any functor which defines a join method, has an of method, and
obeys a few laws is a monad. ”
• Il metodo join si occupa di appiattire: dato che invocare map()
oppure of() più volte può generare una proliferazione di
contenitori che contengono contenitori che contengono
contenitori… serve un modo per appiattire il tutto: in pseudo
codice l’idea è C(C(C(x))).join() = C(C(x))
15
16. FP IN JAVASCRIPT
• Abbiamo detto che esistono librerie che rendono possibile e semplice la programmazione funzionale in JS. Fantasyland
specs mira a fornire le specifiche che tali librerie devono implementare (per i più avventurosi c’è il link più avanti).
• JS fornisce nativamente alcune funzionalità, tramite funzioni già implementate. Si pensi a Array.map():
16
• Per altre funzionalità si possono utilizzare librerie come lodash-fp o ramdajs. Tipicamente:
• Null-check problem: la monade Maybe può essere usata, dato consente di concatenare vari metodi map in
modo da applicare la funzione solo se il contenuto di Maybe è un valore, altrimenti viene tornata la
Monade Maybe.of(null)
• La gestione delle eccezioni con la possibilità di terminare subito dopo un errore: questo può essere gestito
tramite la monade Either, che ha due costruttori: Either.Left e Either.Right. L’idea è quella di memorizzare
l’errore in Left e i valori utili in Right. Sia Left che Right implementano map ma in modo da propagare e
conservare l’errore (un po’ come null nella monade Maybe)
17. FP IN DELPHI
• In Delphi data la facilità con cui si possono definire nuovi tipi, è molto semplice dichiarare che un metodo
(function o procedure) accetta come parametro un altro metodo.
• In ogni caso, il pas System.SysUtils contiene un insieme di dichiarazioni utili di tipi di metodi.
• Con il Delphi 2010 sono stati introdotti i metodi anonimi, sono metodi che possono essere scritti inline
per essere passati come parametro in un altro metodo, rimangono prolissi, ma possono risultare utili
17
18. FP GOOD READINGS
• Articoli introduttivi: https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-
part-1-1f15e387e536#.rqaf0fx53 e https://medium.freecodecamp.org/functional-programming-in-js-with-
practical-examples-part-1-87c2b0dbc276
• Fantasyland spec: https://github.com/fantasyland/fantasy-land
• Professor Frisby’s “Mostly adequate guide to Functional Programming” (https://github.com/
MostlyAdequate/mostly-adequate-guide) divertente e accurato, analizza JavaScript, ma i concetti sono
interamente applicabili aTypeScript e a Java e Delphi in una certa misura
• You don’t know JavaScript (https://github.com/getify/You-Dont-Know-JS) Riguarda solo JS, solo per chi
ama sapere perché le cose funzionano come funzionano.
• Category Theory https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-
preface/, o in alternativa la sua serie di video https://www.youtube.com/watch?v=I8LbkfSSR58
• Video sulla programmazione funzionale in Delphi https://www.youtube.com/watch?v=HDhmUjzUNyQ
18
20. UN PASSO INDIETRO
• Gli Stream, java.util.function, Optional, lambda
expression, method reference…sono un insieme
di strumenti
• rendono possibile un approccio funzionale a
(certi) problemi
20
22. AGGIUNTE:A ITERATOR
• In alcuni casi ho a disposizione solo un
Iterator, quindi non posso usare
Collection.forEach
• Ad esempio
AbstractIndexedLayout.getComponents()
• In questi casi si può usare
Iterator.forEachRemaining
22
1 package java.util;
2
3 import java.util.function.Consumer;
4
5 /**
6 * ...
7 */
8 public interface Iterator<E> {
9
10
11 /**
12 * Performs the given action for each remaining element until all
13 elements
14 * have been processed or the action throws an exception. Actions are
15 * performed in the order of iteration, if that order is specified.
16 * Exceptions thrown by the action are relayed to the caller.
17 *
18 * @implSpec
19 * <p>The default implementation behaves as if:
20 * <pre>{@code
21 * while (hasNext())
22 * action.accept(next());
23 * }</pre>
24 *
25 * @param action The action to be performed for each element
26 * @throws NullPointerException if the specified action is null
27 * @since 1.8
28 */
29 default void forEachRemaining(Consumer<? super E> action) {
30 Objects.requireNonNull(action);
31 while (hasNext())
32 action.accept(next());
33 }
34 }
23. • Vedi la presentazione su Java8
• Tutto il package java.util.function contiene le interfacce e metodi estremamente utili, sia
nell’uso all’interno degli Stream che per un approccio funzionale. Va esplorato
(Function.compose(), Predicate.and()…)
• Voglio trasformare una lista di oggetti X in una mappa con in chiave gli stessi oggetti? ho
il metodo UnaryOperatoridentity
• Voglio filtrare solo gli oggetti nulli o solo i non nulli? ecco ObjectsnonNull e
Objectsnull
• Voglio filtrare una collezione di oggetti recuperando solo quelli di classe AClass, e
ritornare una List<AClass>? (E non è un’aggiunta da Java 8, ma qui tornano
comodi…) Esistono AClassisInstance e AClasscast
INSOMMA:AGGIUNTE!
24. OPTIONAL
• Rappresenta più o meno la monade Maybe:
• è un contenitore che può contenere un valore, o null. Ha quindi i metodi per
ottenere un Optional da un valore o da null
• ha il metodo map che trasforma il suo contenuto se è diverso da null, altrimenti
torna Optional.ofNull
• è generico rispetto a cosa contiene: Optional<Integer> o = Optional.of(3)
• è stato introdotto principalmente per lavorare con gli Stream
• ha altri metodi utili come ad esempio filter: ritorna l’optional stesso se il valore
all’interno verifica il predicato, altrimenti ritorna l’optional di null.
24
25. JAVA STREAM
L’astrazione principale introdotta è lo stream, (in generale uno stream è la
coppia elemento n-simo e la modalità con cui generare il successivo). Sono
generici nel tipo del loro contenuto.
Gli stream sono diversi dalle collezioni per vari motivi:
• è funzionale, (nel senso di FP) quindi operazioni sugli stream producono
un risultato, ma non vanno a modificare la sorgente dello stream
• è intrinsecamente lazy, le operazioni sugli stream sono divise in due tipi:
intermedie (filter, map…) e terminali (collect, sum…). Le operazioni
intermedie sono sempre lazy.
• uno stream non ha necessariamente una fine
• Gli elementi di uno stream sono visitati una sola volta durante la vita
di uno stream, per visitarli di nuovo, va creato un nuovo stream
26. • Ci sono vari metodi per ottenere uno stream, quello tipico è
Collection.stream(), e anche Arrays.stream(). Ci sono inoltre i metodi statici
delle varie classi Stream o IntStream…
• Le operazioni sugli stream sono divise in intermedie e terminali e combinate
assieme in uno stream pipeline che è quindi sempre composto da:
• uno stream sorgente,
• 0 o più operazioni intermedie ed
• una operazione terminale.
• Le operazioni intermedie sono quelle che ritornano un nuovo stream.
Sono sempre lazy nel senso che quando ad esempio si esegue un
operazione di filtro con il metodo filter() questa non esegue il filtro subito,
ma crea un nuovo stream che, una volta attraversato, contiene solo gli
elementi dello stream originale che verificano la condizione del filtro.
• Le operazioni terminali possono visitare tutto o parte dello stream e/
o produrre un side effect. Dopo che l’operazione terminale è stata eseguita
lo stream è consumato e non più riutilizzabile (nella quasi totalità dei casi).
27. PER ESEMPIO (1)
[http://files.zeroturnaround.com/pdf/zt_java8_streams_cheat_sheet.pdf]
Operazioni intermedie
• filter: ritorna uno stream con i soli elementi che soddisfano il
predicato passato
• map: trasforma lo stream in un nuovo stream contenente il
risultato della funzione passata (applicata sugli elementi dello
stream originale)
• distinct: ritorna uno stream di elementi unici nello stream
• limit: ritorna uno stream che contiene solo il numero di
elementi passati
Operazioni terminali
• reduce: serve ad accumulare sugli elementi dello stream
• collect: ritorna una collezione (List, Set…) composta dagli
elementi dello stream
• forEach: per eseguire operazioni che hanno un effetto
secondario sugli elementi
27
28. • Le operazioni intermedie si possono dividere in due sottotipi: stateless e stateful (come
distinct e sorted). Le operazioni di tipo stateful hanno bisogno dello stato dell’elemento
precedente dello stream, inoltre possono aver bisogno di processare l’intero stream prima di
produrre un risultato.
• Gli stream facilitano l’esecuzione parallela del codice, ma in questa sede non approfondiamo,
l’unica cosa che vale la pena ricordare: side-effects prodotti dentro ad uno stream possono portare
a problemi di thread-safety. Quindi cercare di evitare il più possibile cose del genere:
[Stream Javadoc]
[Stream Javadoc]
29. INTERMEDIE
• Abbiamo detto che le operazioni intermedie sono lazy
nel senso che tutte producono un nuovo stream.
• Proprio per questo l’ordine in cui concateno le
operazioni intermedie è estremamente importante:
• se possibile, le operazioni di filter vanno messe per
prime, dato che fanno diminuire il numero di elementi
di input che proseguono lungo la pipeline
29
30. INTERMEDIE: FLATMAP
• FlatMap serve nel caso in cui si stia manipolando un insieme di
oggetti che a loro volta contengono delle collezioni da
manipolare, l’idea è che da ogni elemento dello stream originale si
produca un una serie di stream che verranno accodati assieme.
30
31. TERMINALI: COLLECT
• Collect trasforma gli elementi
dello Stream in un tipo diverso
di risultato.
• Java 8 fornisce la classe
Collectors (toSet(), toMap()…)
• è comunque possibile
implementare un proprio
collector
31
[http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/]
32. TERMINALI: REDUCE
Reduce combina tutti gli elementi di uno stream
in un unico risultato. Sono supportati tre tipi di
reduce.
• Optional<T> reduce(Bifunction<T> accumulator)
• T reduce(T identity, BinaryOperator<T>
accumulator)
32
[http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/]
• <U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
La terza firma di reduce si utilizza (e
il combiner viene effettivamente
invocato) se si eseguono gli stream
in parallelo. L’idea è che il combiner
viene eseguito quando arriva il
momento di rimettere assieme le
varie parti eseguite in parallelo.
33. UTILI
• IntStream.range(startIncluded, endExcluded) e IntStream.of al posto di un ciclo for.
• Esistono gli stream di primitive: IntStream, LongStream e DoubleStream. Essi supportano particolari funzioni
terminali come sum() e average(). Inoltre ciascuno ha il metodo boxed() per passare allo Stream
corrispondente: IntStream.boxed() produce uno Stream<Integer>. Il contrario può essere fatto con i
metodi mapToInt(), mapToLong() e mapToDouble().
• Da array a stream e viceversa: Arrays.stream() e Stream.toArray()
• La concatenazione di stream: Stream.concat() o Stream.of()
• Non è possibile riutilizzare uno stesso Stream, ma si può usare Supplier:
• A volte non serve usare gli stream, se si vuole semplicemente iterare su una Collection si può utilizzare
forEach()
• Collectors (toList, toMap, groupingBy e un Collector ad hoc)
33
[http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/]
34. FP O IMPERATIVE?
Di base va tenuto presente un unico concetto: è poco leggibile (a volte pericoloso) e poco sensato mescolare
programmazione imperativa e funzionale. Se si sta scrivendo un algoritmo usando uno stile, si deve cercare di evitare
di usare simultaneamente anche l’altro (quanto meno chiedersi e investigare se si può fare meglio)
• Le IDE danno ottimi suggerimenti sul come migliorare il codice, analizziamo i warning che danno:
• Ad esempio: usiamo i reference method, non stream.map(placeplace.getName()) ma stream.map(PlacegetName)
• Non stream().xxx.collect(toList()).toArray(new String[]{}) ma stream().xxx.toArray(String[]new)
Terminale
Stream sorgente
35. PER ESEMPIO (2)
Diventa, evitando di passare da funzionale a imperativo:
Torna un Optional<Filter>
Torna un Optional<Boolean>
38. JAVA 8 E FP: LIMITAZIONI
• Non esiste un’implementazione di Either
• Optional è stata introdotta solo dopo molte discussioni e solo per il suo
uso per gli Stream, viene consigliato di non usarla al di fuori di quel contesto
• Le varie interfacce in java.util.function non appartengono ad una stessa
gerarchia comune, quindi non si riescono a comporre (in senso matematico)
dato che solo Function ha i metodi compose e andThen
• Tuttavia le lambda possono diventare qualunque cosa (nei limiti del loro
tipo), basta forzare la type inference: .filter(((Predicate<ThingMO>)
this::isInlet).negate())
38