88
Programmazione funzionale Pistoia 2017 con Scala [email protected] @gundam79 linkedin.com/in/stefanoperuzzi

Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Embed Size (px)

Citation preview

Page 1: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Programmazione funzionale

Pistoia2017

con Scala

[email protected]

@gundam79

linkedin.com/in/stefanoperuzzi

Page 2: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

La PF è una novità?

IPL, LISP(anni 50)

Page 3: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

La PF è una novità?

IPL, LISP(anni 50)

ML, Scheme(anni 70)

Page 4: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

La PF è una novità?

IPL, LISP(anni 50)

ML, Scheme(anni 70)

Haskell(anni 90)

Page 5: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

La PF è una novità?

IPL, LISP(anni 50)

ML, Scheme(anni 70)

Haskell(anni 90)

OCaml

Erlang(Ericsson)

F#.NET

Page 6: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

La PF è una novità?

IPL, LISP(anni 50)

ML, Scheme(anni 70)

Haskell(anni 90)

OCaml

Erlang

F#.NET

ScalaJVM

Page 7: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

La PF nei linguaggi più diffusiCercando "functional" su manning.com e oreilly.com:

● Functional Programming in Java● Functional Programming in C#● Real-World Functional Programming (With examples in F# and C#)● Functional Programming in JavaScript● Functional Programming in C++● Functional Programming in Python● Functional Programming in PHP

Non è obbligatorio imparare un nuovo linguaggio per usare la PF!

Page 8: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Preparare l’ambiente Scala● Installare Java JDK

● Scaricare la distribuzione: http://www.scala-lang.org/download/

● Usare un editor con syntax highlighting, es.: Atom

● Ancora meglio: un IDE, consigliato https://www.jetbrains.com/idea/ con

plugin per Scala

● Provate la REPL, basta lanciare “scala” in una console

Page 9: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Cos’è la programmazione funzionale

Page 10: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Cos’è la PFPrincipio fondamentale della Programmazione Funzionale:

costruire programmi usando pure functions => functions without side effects, funzioni senza “effetti collaterali”.

Esempi di side effects:

● Modificare una variabile (globale) o una struttura dati● Lanciare un’eccezione● Leggere/scrivere un file

Page 11: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Cos’è una pure functionDefiniamo la funzione f: A => B (da valori di tipo A a valori di tipo B) una computazione che trasforma ogni valore a di tipo A in un valore b di tipo B.

Esempio di funzioni:

● length: String → Int ● +: (Int, Int) → Int

Se f è una funzione (pura) non avrà altri effetti (side effects) che tornare il suo risultato. Length e + sono funzioni pure.

Page 12: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Referential transparency e substitution modelUn'espressione (e in particolare una funzione) è referentially transparent se, per ogni programma p, sostituendo l’espressione con la sua valutazione non si modifica p.

● l’espressione (2 + 3) può essere sostituita con 5● Se x = sqrt(2), si può sostituire ogni occorrenza di x nel programma con

sqrt(2)

Definizione: una funzione f è pura se f(x) è referentially transparent per tutte le espressioni x a sua volta referentially transparent. In pratica, se anch’essa è sostituibile con la sua valutazione.

Page 13: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Esempio di referential transparencyIn Scala: val x = "Hello World" val r1 = x.reverse val r2 = x.reverse

→ r1 e r2 sono uguali

Possiamo applicare il principio di sostituzione: x → “Hello World” val x = "Hello World" val r1 = "Hello World".reverse val r2 = "Hello World".reverse

→ r1 e r2 sono uguali

Page 14: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Esempio di (not) referential transparency val x = new StringBuilder("Hello") val y = x.append(" World") val r1 = y.toString // r1 = "Hello World" val r2 = y.toString // r2 = "Hello World"

→ r1 e r2 sono uguali

Sostituzione: y → x.append(" World")

Page 15: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Esempio di (not) referential transparency val x = new StringBuilder("Hello") val y = x.append(" World") val r1 = y.toString // r1 = "Hello World" val r2 = y.toString // r2 = "Hello World"

→ r1 e r2 sono uguali

Sostituzione: y → x.append(" World")

val x = new StringBuilder("Hello") val r1 = x.append(" World").toString // r1 = "Hello World" val r2 = x.append(" World").toString // r2 = "Hello World World"→ r1 e r2 sono diversi!

La funzione append() di StringBuilder non è una pure function.

Page 16: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

I benefici della referential transparencyVi sembra complicato? Perché è complicato!

Per lo più debugging implica ricostruire l’evoluzione dello stato di un programma.

Il substitution model è molto più facile!

Page 17: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

I benefici della referential transparencyLe funzioni pure:

● separano la logica della computazione da “come si ottengono gli input” e da “cosa fare con gli output”

● permettono il local reasoning, perché la computazione non dipende dal contesto● sono più modulari ⇒ più facili da riutilizzare● sono facilmente testabili● sono componibili

Page 18: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

E i cicli?

def factorial(n: Int): Int = { var result = 1

for(i <- Range(1,n)) { result = result * (i + 1) } result }

Un esempio: in scala “if” è una funzione che torna un risultato: la valutazione del ramo true o quella del ramo false. Al contrario i cicli non sono funzioni, non tornano un valore.

I cicli hanno intrinsecamente il concetto di stato (continuo il ciclo finché non succede qualcosa). Ad esempio:

Page 19: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Cicli → ricorsione

def factorial(n: Int): Int = {

def go(n: Int, acc: Int): Int = { if (n == 1) acc

else go(n-1, acc * n) }

go(n, 1) }

Fattoriale senza stato:

È più facile “da leggere”? Ma la ricorsione non era “il male”?!?

Page 20: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Tail recursion def factorial(n: Int): Int = {

@tailrec def go(n: Int, acc: Int): Int = { if (n == 1) acc

else go(n-1, acc * n) }

go(n, 1) }

Se l’ultima espressione di una funzione è la chiamata ricorsiva, il compilatore sa tradurre la ricorsione in un ciclo iterativo. Quindi no StackOverflowException.

Page 21: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Un esempio un po’ più completoImmaginiamo un semplice programma che

● Modellizza un caffè e un metodo di pagamento con carta di credito● Permette di comprare un caffè con la carta● Permette di comprare tanti caffè con la carta

Page 22: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Buy some Coffeeclass Coffee { val prezzo = 1.0}

class CartaCredito { def charge(p: Double) = { println(s"Pagato $p euro tramite carta di credito") }}

object App { /** * Program entry point. */ def main(args: Array[String]) {

... }}

Page 23: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Buy some Coffeeobject App {

def main(args: Array[String]) { val cc = new CartaCredito println("Result is " + buyCoffee(cc))

val coffees = buyCoffees(cc, 10) println(s"comprati ${coffees.length} caffe'") }

}

Page 24: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Buy some Coffee def buyCoffee(cc: CartaCredito): Coffee = { val coffee = new Coffee() cc.charge(coffee.prezzo)

coffee }

Page 25: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Buy some Coffee def buyCoffee(cc: CartaCredito): Coffee = { val coffee = new Coffee() cc.charge(coffee.prezzo)

coffee }

Domanda: è una funzione pura?

In altre parole: ha degli effetti collaterali?

Page 26: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

def buyCoffee(cc: CartaCredito): Coffee = { val coffee = new Coffee() cc.charge(coffee.prezzo)

coffee }

def buyCoffees(cc: CartaCredito, n: Int): List[Coffee] = { // con var result e' ri-assegnabile var result = List[Coffee]()

for (i <- Range(0, n)) result = buyCoffee(cc) :: result

result }

Page 27: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Vediamo il progetto completo...

Page 28: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Un passo avantibuyCoffee() si occupa di preparare il caffè e pagare. Troppe cose per una sola funzione!

Riscriviamo in modo che:

● Prepari il caffé● Imposti il conto, senza pagarlo

E non compia altre “azioni” che non gli competono.

Per far questo introduciamo la classe Payment.

Page 29: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Buy some pure Coffeecase class Payment(cc: CartaCredito, amount: Double) { def pay() { cc.charge(amount) }}

/////

def buyCoffee(cc: CartaCredito): (Coffee, Payment) = { val coffee = new Coffee() val payment = Payment(cc, coffee.prezzo)

(coffee, payment) }

Una coppia di oggetti. Detto c questo dato, si accede ai singoli oggetti con c._1 e c._2

Page 30: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Buy some pure Coffee def main(args: Array[String]) { val cc = new CartaCredito val coffeePayment = buyCoffee(cc) println("Ho preso un " + coffeePayment._1) coffeePayment._2.pay()

val coffees = buyCoffees(cc, 10) println(s"comprati ${coffees.length} caffe', da pagare!") coffees.map(_._2.pay()) }

////

def buyCoffees(cc: CartaCredito, n: Int): List[(Coffee, Payment)]

Page 31: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Vediamo il progetto con l’introduzione di Payment

Page 32: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

High Order FunctionsUn’altro concetto della PF è che le funzioni sono valori. Come ogni valore, le funzioni hanno un tipo. Ad esempio:

È una funzione con un argomento Int, e valore di ritorno Int. Si scrive così:

Questa funzione ha lo stesso tipo:

def factorial(n: Int): Int

Int => Int

def quadrato(n: Int): Int

Page 33: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

High Order FunctionsPossiamo definire la funzione stampa:

Definizione: una high-order function è una funzione che accetta altre funzioni come argomento.

Analogamente si definiscono le funzioni come first-class value, valori "di prima classe". Come ogni altro valore possono essere passate come argomento o tornate come risultato.

def stampa(x: Int, f: Int => Int) = { println("Dato " + x + " il risultato e’ " + f(x)) }

Page 34: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Anonymous functionsNon è necessario dare un nome alla funzione, possiamo definirla in modo anonimo:

Si può evitare di specificare il tipo dell’argomento, se il compilatore è in grado di inferirlo dal contesto:

stampa(5, (x: Int) => x*7)

stampa(5, x => x/2)

Una funzione anonima con un argomento Int

Page 35: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Un altro esempio di function compositionVediamo la somma di interi da a a b:

La somma del quadrato di interi da a a b:

def sumInts(a: Int, b: Int): Int = if (a > b) 0 else a + sumInts(a + 1, b)

def square(x: Int): Int = x * x

def sumSquares(a: Int, b: Int): Int = if (a > b) 0 else square(a) + sumSquares(a + 1, b)

Page 36: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Un altro esempio di function compositionVediamo la somma delle potenze di 2 da a a b:

In pratica si calcola ∑ da a a b di una funzione f, con f = sum, square, powerOfTwo.

Possiamo riscrivere generalizzando f.

def powerOfTwo(x: Int): Int = if (x == 0) 1 else 2 * powerOfTwo(x - 1)

def sumPowersOfTwo(a: Int, b: Int): Int = if (a > b) 0 else powerOfTwo(a) + sumPowersOfTwo(a + 1, b)

Page 37: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

HOF sum()Vediamo la somma di f(x) per x da a a b:

sum() è una high-order function, con essa possiamo riscrivere le funzioni precedenti:

def sum(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f, a + 1, b)

def id(x: Int): Int = xdef sumInts(a: Int, b: Int): Int = sum(id, a, b)def sumSquares(a: Int, b: Int): Int = sum(square, a, b)def sumPowersOfTwo(a: Int, b: Int): Int = sum(powerOfTwo, a, b)

Page 38: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

sumInts è una funzione di tipo (Int, Int) => Int, come restituito da sum.

CurryingVediamo come distinguere l'intervallo (a, b) dalla funzione di somma:

In questa formulazione, sum ha come argomento una funzione e torna una nuova funzione sumF, ed è quest'ultima a prendere come parametro l'intervallo (a, b).

def sum(f: Int => Int): (Int, Int) => Int = { def sumF(a: Int, b: Int): Int = if (a > b) 0 else f(a) + sumF(a + 1, b) sumF}

def sumInts = sum(x => x)def sumSquares = sum(x => x * x)def sumPowersOfTwo = sum(powerOfTwo)

sumSquares(1, 10) + sumPowersOfTwo(10, 20)

Page 39: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

CurryingÈ possibile invocare la somma in questi due modi analoghi:

Si definisce currying la tecnica che trasforma una funzione con più argomenti nell'invocazione di una catena di funzioni, ognuna con un sottoinsieme degli argomenti di partenza.

Fu definita da Moses Schonfinkel e riscoperta successivamente da Haskell Curry, da cui il nome.

sum(squares, 1, 10) = sum(square)(1, 10)

Page 40: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Collections & PF

Page 41: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

ListLa classe List di Scala è una single linked list. È un buon esempio di struttura dati in cui sono usate le tecniche viste fino qui.

Nil è l'oggetto Lista vuota. List(...) è il metodo per costruire una List a partire dagli oggetti contenuti. :: pre-pende un oggetto, ++ unisce due List

val a = List[Int](1,2,3) val b = Nil val c = List() val d = List(3,7,1,10,4,9)

val e = 0 :: a

// lists are immutable: this does not compile! //a = 0 :: a

val f = a ++ d

Page 42: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Esempi con List: elaborazioneEseguiamo operazioni su tutti gli elementi della lista. È un lavoro per la ricorsione.Es.: somma degli elementi di una lista def sum(l: List[Int]): Int = ???

Page 43: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Esempi con List: elaborazioneEseguiamo operazioni su tutti gli elementi della lista. È un lavoro per la ricorsione.Es.: somma degli elementi di una lista

Domanda: e' una funzione tail recursive?

def sum(l: List[Int]): Int = { if (l.isEmpty) 0 else l.head + sum(l.tail) }

Page 44: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

def sum(l: List[Int]): Int = { if (l.isEmpty) 0 else { println("sommo " + l.head) val r = l.head + sum(l.tail) println("risultato " + r) r }}

Esempi con ListProviamo a stampare qualcosa:

Page 45: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

def sum(l: List[Int]): Int = { @annotation.tailrec def sumAcc(l: List[Int], sum: Int): Int = { if (l.isEmpty) sum else sumAcc(l.tail, sum + l.head) }

sumAcc(l, 0)}

Esempi con List: accumulatoreTrick: usiamo un accumulatore per evitare espressioni che coinvolgono la ricorsione, impedendo la tail recursion.

Page 46: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

def square(l: List[Int]): List[Int] = ???

Da List a ListOttenere una nuova lista di elementi trasformati.Ad es. trasforma ogni intero nel suo quadrato:

Page 47: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

def square(l: List[Int]): List[Int] = { if (l.isEmpty) Nil else (l.head * l.head) :: square(l.tail) }

def neg(l: List[Int]): List[Int] = if (l.isEmpty) Nil else (-l.head) :: neg(l.tail)

Da List a ListOttenere una nuova lista di elementi trasformati.Ad es. trasforma ogni intero nel suo quadrato:

Trasforma ogni elemento nel suo negato:

Page 48: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

def transform(l: List[Int], f: (Int => Int)): List[Int] = ???

Trasformazione di una ListGeneralizziamo: data una generica funzione f() che trasforma interi in interi…

Page 49: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

def transform(l: List[Int], f: (Int => Int)): List[Int] = { if (l.isEmpty) Nil else f(l.head) :: transform(l.tail, f)}

def negate(x: Int) = -xdef sqr(x: Int) = x * x

transform(a, negate)transform(a, sqr)

Trasformazione di una ListGeneralizziamo: data una generica funzione f() che trasforma interi in interi…

Trasformare una collezione di oggetti è un’operazione molto comune, tanto che List fornisce una funzione ad hoc per questo.

Page 50: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

def map[B](f: (A) => B): List[B]

Builds a new collection by applying a function to all elements of this list.

List mappingDefinizione di map() per una List[A] (Scala API docs):

Il risultato è una nuova List di valori di tipo B, ottenuta applicando f a ogni elemento di tipo A della List di partenza.

Questa operazione si definisce trasformazione, o proiezione, o mapping.

Page 51: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

a.map(negate)

b.map( (x: Int) => x % 2 )

b.map( (x: Int) => x % 2 == 0)

val bs = b.map( (x: Int) => if (x % 2 == 0) x + " e' pari" else x + " e' dispari" )

bs.mkString("\n")

List mappingAlcune operazioni di mapping, dove a e b sono una List[Int]:

Page 52: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Collection mapping in Java 8Anche Java ha il mapping delle collezioni:

Scala è davvero un linguaggio così complesso?

public class Employee { private String name; private Integer age; private Double salary;

// getters and setters}

List<String> employeeNames = employeeList .stream() .collect(Collectors.mapping(Employee::getName, Collectors.toList()));

Page 53: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Altre funzioni di ListOttenere una lista di elementi che soddisfano una condizione:

Verificare se una condizione vale per tutti gli elementi della lista:

b.filter( (x: Int) => x % 2 == 0)

b.forall( (x: Int) => x < 100 && x > 0)

Page 54: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Folding, that is: reduceOltre al mapping, l'altra operazione sui dati è reduce, o folding.

Sono le operazioni che, data una lista, riducono a un solo valore, in funzione di tutti i valori della lista. Un esempio banale: la somma di tutti gli interi.val sum = (x1: Int, x2: Int) => x1 + x2val l = List(1,5,7)l.foldLeft(0)(sum)

Page 55: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Folding, that is: reduceOltre al mapping, l'altra operazione sui dati è reduce, o folding.

Sono le operazioni che, data una lista, riducono a un solo valore, in funzione di tutti i valori della lista. Un esempio banale: la somma di tutti gli interi.val sum = (x1: Int, x2: Int) => x1 + x2val l = List(1,5,7)l.foldLeft(0)(sum)

0 + 1 = 1

Page 56: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Folding, that is: reduceOltre al mapping, l'altra operazione sui dati è reduce, o folding.

Sono le operazioni che, data una lista, riducono a un solo valore, in funzione di tutti i valori della lista. Un esempio banale: la somma di tutti gli interi.val sum = (x1: Int, x2: Int) => x1 + x2val l = List(1,5,7)l.foldLeft(0)(sum)

0 + 1 = 1

1 + 5 = 6

Page 57: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Folding, that is: reduceOltre al mapping, l'altra operazione sui dati è reduce, o folding.

Sono le operazioni che, data una lista, riducono a un solo valore, in funzione di tutti i valori della lista. Un esempio banale: la somma di tutti gli interi.val sum = (x1: Int, x2: Int) => x1 + x2val l = List(1,5,7)l.foldLeft(0)(sum)

0 + 1 = 1

1 + 5 = 6

6 + 7 = 13

Page 58: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Folding APIScala API, foldLeft per List[A]:

def foldLeft[B](z: B)(op: (B, A) ⇒ B): B

Applies a binary operator to a start value and all elements of this sequence, going left to right.

Quindi il tipo B del risultato è quello dell’operatore. Ad esempio, la stessa stringa di interi:l.foldLeft("0")((s: String, i: Int) => s + " + " + i)

Page 59: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Fold RightScala API, foldRight per List[A]:

def foldRight[B](z: B)(op: (A, B) ⇒ B): B

Applies a binary operator to all elements of this list and a start value, going right to left.

Ad esempio, qual è il risultato dil.foldRight("0")((i: Int, s: String) => s + " + " + i)

Page 60: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Collection di PersoneQualche esempio con la più classica delle class:case class Persona(nome: String, cognome: String, eta: Int)... val pippo = Persona("Pippo", "Rossi", 25) val mario = Persona("Mario", "Rossi", 28) val giovanni = Persona("Giovanni", "Neri", 19) val marco = Persona("Marco", "Bianchi", 36) val roberto = Persona("Roberto", "Verdi", 41) val laura = Persona("Laura", "Bianchi", 20) val michele = Persona("Michele", "Rossi", 15)

val people = List[Persona](pippo, mario, giovanni, marco, roberto, laura, michele)

Page 61: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Collection di PersoneCome si ottiene la lista dei nomi completi (nome e cognome)?

Come si trovano tutte le persone sotto i 24 anni?

Qual è l’età media?

Come si trovano tutti i parenti, cioè le persone con stesso cognome?

Page 62: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Eccezioni & PF

Page 63: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Eccezioni: perché no?All’inizio abbiamo elencato le eccezioni tra i side effect, incompatibili con la PF. Infatti le eccezioni non sono Referentially Transparent.

def MyFunc(i: Int): Int = { val x = 42 / i try { x } catch { case e: Exception => 55 }}

Se invoco MyFunc(0) ottengo un’eccezione.

Se sostituisco 42/i a x, ottengo 55!

Page 64: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Eccezioni: perché no?RT significa ragionare localmente, non in dipendenza dal contesto. Nell’esempio 42/i cambia elaborazione in base alla sua posizione, quindi dipendentemente dal contesto.

Inoltre il catch delle eccezioni rappresenta un salto incondizionato => codice difficile da comprendere e manutenere.

Da qui la regola generale:

usate le eccezioni per la gestione degli errori, non per il controllo del flusso.

Ma è solo un buon consiglio!

Page 65: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Eccezioni: anche noLe eccezioni non sono type safe. La funzione che abbiamo scritto:

def MyFunc(i: Int): Int

non dice nulla di possibili eccezioni. Se queste vengono “lanciate”, nessuno garantisce che il chiamante sia in grado di gestirle.

Page 66: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Eccezioni: anche noLe eccezioni non sono type safe. La funzione che abbiamo scritto:

def MyFunc(i: Int): Int

non dice nulla di possibili eccezioni. Se queste vengono “lanciate”, nessuno garantisce che il chiamante sia in grado di gestirle.

E le checked exception di Java?

In Java una funzione dichiara quali eccezioni può lanciare, e il caller deve gestirle (o rilanciarle).

Ma è davvero così utile?

Page 67: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Catchin’ exceptions: la disfatta!In realtà, alcuni ricercatori(1) hanno analizzato, tramite automatismo sw, progetti Java su GitHub e SourceForge.

Hanno così raccolto varie statistiche sul contenuto dei catch block.

(1) Analysis of Exception Handling Patterns in Java Projects: An Empirical Study - Suman Nakshatri, Maithri Hegde, Sahithi Thandra

Page 68: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma
Page 69: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Eccezioni nella PFLe checked exceptions non sono applicabili alle high-order functions.

Ad esempio list.map(f) ha come argomento una funzione f().

Che eccezioni deve gestire map()?

Come può gestire ogni possibile eccezione di ogni possibile f()?

In generale non è possibile. Occorre una gestione degli errori che sia compatibile con la metodologia funzionale.

Page 70: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Eccezioni nella PFdef mean(xs: List[Double]): Double = if (xs.isEmpty) throw new ArithmeticException("mean of empty list!") else xs.sum / xs.length

La funzione mean (media) è una partial function: non può essere calcolata per tutto il dominio di input.

Si potrebbe tornare un valore di guardia (c like): null, oppure 0.

Page 71: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Eccezioni nella PFdef mean(xs: List[Double]): Double = if (xs.isEmpty) null else xs.sum / xs.length

Problemi:

- non c’è controllo che il chiamante verifichi la guardia (per il compilatore, è trasparente)

- potrebbe non esserci un valore di guardia, ad esempio per i tipi primitivi

Page 72: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

OptionIn Scala è definito il tipo Option[A] che ammette due soli valori:

● Some

● None

Some è un oggetto che contiene un valore di tipo A, che si può ottenere invocando get(). Con Option possiamo scrivere:

def mean(xs: List[Double]): Option[Double] =

if (xs.isEmpty) None

else Some(xs.sum / xs.length)

Page 73: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Optionval l = List(3d,5d,7d,11d)val m: Option[Double] = mean(l)m.get // torna 6.5

val n = mean(List())n.get // throws NoSuchElementException

if (n.isDefined) n.get else 0d // torna 0.0

// equivalente:n.getOrElse(0d)

Page 74: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

OptionOption ha tutte le funzioni viste per List, ad esempio map():

def map[B](f: (A) ⇒ B): Option[B]

Returns a Some containing the result of applying f to this Option's value if this Option is nonempty. Otherwise return None.

La funzione f() viene applicata solo se Option è definito, quindi se contiene un valore di tipo A. Quindi questa funzione opera sempre sul valore contenuto in un Some, non dovendosi preoccupare di altro.

Grazie a map(), possiamo concentrarci “on the happy path!” (cit. Erik Meijer) e rimandare la gestione errori al momento più opportuno.

Page 75: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

OptionTornando all’esempio della funzione mean(), dove m è Some(6.5) e n è None:

def toPercent(d: Double): String = d + "%"

m.map(toPercent)n.map(toPercent)

Tutt’e due le chiamate sono valide, e tornano...

Page 76: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Persone con OptionRiprendiamo la classe Persona, possiamo creare una funzione che cerca per nome nella lista people:

val people = List[Persona](pippo, mario, ...

def lookupByName(name: String): Option[Persona] = { val l = people.filter(p => p.nome == name) if (l.isEmpty) None else Some(l.head)}

Page 77: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Persone con OptionlookupByName("Paolo")lookupByName("Marco")

lookupByName("Paolo").map(_.eta).getOrElse(0)

lookupByName("Michele") .map(_.eta) .filter(_ > 18) .getOrElse(0)

Page 78: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Vediamo come gestire carte di credito che possono fallire

Page 79: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Uno stato funzionale

Page 80: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Generatore di numeri randomAnche Scala ha il "classico" generatore di numeri random:

val rng = new scala.util.Random

rng.nextInt

rng.nextInt(10)

nextInt() è una funzione pura?

Evidentemente no. Pensate a come testarla, ad esempio come testare:

val rng = new scala.util.Randomdef rollDie: Int = { rng.nextInt(6)}

Page 81: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Rendere le API con stato purePrendiamo una API che modifica uno stato interno (MyState):

class Foo { private var state: MyState = ... def bar: Bar def baz: Int}

Ipotesi: bar e baz sono funzioni che si basano su "state" e modificano (mutano) "state".

Come rendere tutto funzionale?

Page 82: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Rendere le API con stato pureStessa API, con stato "esplicito":

trait Foo { def bar: (Bar, Foo) def baz: (Int, Foo)}

bar e baz sono funzioni pure, la loro esecuzione è senza effetti collaterali.

Chi usa questa API è però responsabile di passare il giusto Foo nella chiamata.

Page 83: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Random Generator con stato funzionaletrait RNG { def nextInt: (Int, RNG)}

case class SimpleRNG(seed: Long) extends RNG { def nextInt: (Int, RNG) = { ... val nextRNG = SimpleRNG(newSeed) (n, nextRNG) }}

Page 84: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Random Generator con stato funzionaleEsempi di utilizzo della API:

def randomPair(rng: RNG): ((Int,Int), RNG) = { val (i1,rng2) = rng.nextInt val (i2,rng3) = rng2.nextInt ( (i1,i2), rng3 )}

def nonNegativeInt(rng: RNG): (Int, RNG) = { val (i, r) = rng.nextInt (if (i < 0) -(i + 1) else i, r)}

Nota: Int.Min = - Int.Max - 1 In questo modo -Int.Min diventa Int.Max

Page 85: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Function compositionQuindi dobbiamo sempre passare lo stato tra una chiamata e l'altra?

No, in generale le ripetizioni vengono "fattorizzate" tramite la composizione di funzioni.

type Rand[A] = RNG => (A, RNG)

def map[A,B](s: Rand[A])(f: A => B): Rand[B] = rng => { val (a, rng2) = s(rng) (f(a), rng2) }

map() esegue la transizione di stato, e applica una funzione al dato a.

Page 86: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Function compositionCon map() si può creare un generatore di numeri non negativi, e pari:

def nonNegativeEven: Rand[Int] = map(nonNegativeInt)(i => i - i % 2)

Magicamente non dobbiamo più occuparci di passare lo stato,

map() fa tutto il lavoro per noi.

Page 87: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

ConclusioniIn definitiva, ogni programma dotato di uno stato può essere trasformato in un programma funzionale.

Il metodo diretto è creare sempre un nuovo, immutabile stato ad ogni transizione dello stesso.

Grazie alla composizione di funzioni (e ce ne sono tante, map è solo un esempio) è facile ottenere logiche complesse con poche righe di codice.

Page 88: Programmazione con Scala funzionalecdn.ing4.it/formazione/upload/fotoutenti/pistoia/Programmazionefu... · Principio fondamentale della Programmazione Funzionale: ... Vediamo la somma

Bibliografia e web-ografia● Functional Programming in Scala - PAUL CHIUSANO,

RÚNAR BJARNASON - Manning● Scala By Example - Martin Odersky - EPFL● https://en.wikipedia.org/wiki/Functional_programming

I sorgenti usati sono disponibili qui:

https://github.com/midthegap/progfun