Linguaggi e Paradigmi di Programmazione
universityProf: Luca Padovani
Info Corso
- Orario
- Lun 16-18
- Gio 9-11
- Ven 16-18
 
- Testi
- Links
- Esame
- Esercizi di programmazione Haskell in tempo limitato
- Teoria
- Fondamenti del paradigma funzionale
 
- Esercizi (Java 8) e Domande - 9 CFU
 
Teoria
Introduzione
Il paradigma funzionale mette le funzioni allo stesso livello delle variabili
- é possibile definire funzioni che trasformano funzioni, che restituiscono funzioni, che utilizzano funzioni in input
Paradigmi e Storia
- imperativo
- C, Pascal
- programma come sequenza di comandi da eseguire
- l’esecuzione modifica lo stato della macchina ad ogni istruzione
 
 
- object-oriented
- Smalltalk
- oggetti che comunicano tra loro
- permette di gestire piu' facilmente la complessita'
 
- programma come interazioni (metodi) tra oggetti
- le interazioni modificano lo stato della macchina
- i metodi sono riconducibili al paradigma imperativo
 
 
- funzionale
- Haskell
- espressione valutazionale restituisce un risultato
 
Negli anni:
- ‘50
- Assembly
- FORTRAN
- formula translator
- procedure, espressioni
 
- COBOL
- per gestione aziendale, introduce il record
 
 
- ‘60
- ALGOL
- forma BNF
- indipendente dall’architettura
- blocchi, variabili locali, ricorsione
 
- BASIC
- facile da imparare ma poco strutturato
 
- Simula 67
- classi, oggetti, ereditarieta’
 
 
- ALGOL
- ‘70
- Pascal
- programmazione strutturata, tipi per evitare errori
- ideale per imparare a programmare
 
- Smalltalk
- programmazione ad oggetti estrema, tutto e’ un oggetto
- l’unica operazione possibile e’ l’invio di messaggi, metodi
 
- C
 
- Pascal
- ‘80
- Ada
- evoluzione di Pascal con concorrenza e tipi astratti
- sviluppado dal ministero della difesa USA
 
- C++
- PostScript
- scripting basato sullo stack, interpretato dalle stampanti
 
- Perl
- linguaggio interpretato
 
 
- Ada
- ‘90
- Python
- Java
- PHP
- JavaScript
 
Lambda-calcolo
Ci si restringe sull’essenza della programmazione in linguaggio funzionale
- si distilla un piccolo linguaggio facilmente studiabile
Differenze tra linguaggio e calcolo
- il calcolo é confluente
- il risultato non dipende dalle azioni intraprese per raggiungerlo
 
- non é deterministico
Concetti
- processo di valutazione / riduzione
- funzioni con singolo argomento / currificate(Haskell Curry)- funzioni di ordine superiore
- accettano come argomenti funzioni / restituiscono altre funzioni
 
- agendo per specializzazioni
 
- funzioni di ordine superiore
- linguaggi
- eager
- lazy
 
- Sistema di Tipi/ Algoritmo diInferenza
Funzioni
- 
Punto di vista estensionale \(f = \{(0,1),(1,2),(2,5),\cdots\}\) 
- 
Punto di vista intensionale \(f(x) = x^{2} + 1\) 
Sintassi
- Variabili
- \(Var = \{x,y,z.\cdots\}\)
- infinito
 
 
- \(Var = \{x,y,z.\cdots\}\)
- Sintassi
- \(M,N ::= x \mid (\lambda x.M) \mid (M N)\)
 
- Terminologia
- \((\lambda x.M)\) astrazione o funzione con argomento \(x\) e corpo \(M\)
- \((M N)\) applicazione della funzione \(M\) all’argomento \(N\)
 
- Esempi
- \((\lambda x.x)\) - Funzione Identitá
- \(((\lambda x.(xx))(\lambda y.(yy)))\) - loop infinito
- \((\lambda f.(\lambda x.(f(f x))))\)
 
- 
Convenzioni Sintattiche - parentesi esterne omesse
- corpo delle astrazioni si estende a destra
- a destra del punto
 
- l’applicazione é associativa a sinistra
 
- 
Variabili Libere e Legate - \(x\) in \(M\) é legata se compare in sotto-termine
- diciamo che un’occorrenza di \(x\) in \(M\) é libera altrimenti
 Esempi - \(\lambda x.\: x\)
- nessuna variabile libera => termine chiuso
 
- \(x \: y \: z\)
- tutte le variabili sono libere
 
- \((\lambda x.\: x \: y) \: x\)
- \(x\) sia legata che libera
 
 - 
Sostituzione - \(M\{N/y\}\) é ottenuta sostituendo le occorrenze libere di \(y\) in \(M\) con \(N\)
- evitare la cattura delle variabili libere di \(N\) per non alterarne il senso
- le variabili libere sono definite esternamente allo scope della astrazione, non posso modificarle
 
 
 
Relazioni di Equivalenza
- 
α-conversione \(y \notin fv(M) \implies \lambda x.M \: \iff_{\alpha}\: \lambda y.M\{y/x\}\) congruenza tra λ-espressioni tali che hanno lo stesso corpo, solo con nome dell’argomento diverso 
- 
β-riduzione Applicare una funzione \(\lambda x.M\) a un argomento \(N\) significa valutare il corpo in cui ogni occorrenza libera dell’argomento \(x\) é sostituita con \(N\). \((\lambda x.M) \: N \rightarrow_{\beta}M\{N/x\}\) - \(M \rightarrow_{\beta}M^{'} \implies M \: N \rightarrow_{\beta}M^{'}\:N\)
 Da redex(reducible expression) aridotto- \((\lambda x.M) \: N\)
- \(M\{N/x\}\)
 
- 
η-riduzione \(x \notin fv(M) \implies \lambda x.M \: x \rightarrow_{\eta}M\) - \(M \rightarrow_{\eta}M^{'} \implies M\: N \rightarrow_{\eta}M^{'}\: N\)
- \(M \rightarrow_{\eta}M^{'} \implies N\: M \rightarrow_{\eta}N \: M^{'}\)
- \(M \rightarrow_{\eta}M^{'} \implies \lambda x.M \rightarrow_{\eta} \lambda x.M^{'}\)
 
- 
Convertibilitá \(N\rightarrow M \land M\rightarrow N \implies M \leftrightarrow N\) - \(\Leftrightarrow\) indica la chiusura riflessiva e transitiva di \(\leftrightarrow\)
 
- 
Confluenza Teorema: - \(M \Rightarrow N_{1} \land M \Rightarrow N_{2} \implies \exists N \mid N_{1} \Rightarrow N \land N_{2} \Rightarrow N\)
- l’ordine delle riduzioni del β-redex non importa
- il teorema si generalizza in \(n\) \(N\)
 Questo risultato é importante in quanto non risulta per nessun altro linguaggio di programmazione - in quanto la memoria puó essere modificata dall’esecuzione, l’ordine diventa fondamentale
- al contrario del lambda calcolo che é un linguaggio puro
- come Haskell, nella sua versione piú pura
 
- come 
 
- al contrario del lambda calcolo che é un linguaggio puro
- Es: l’assegnamento é una espressione mista, sia espressione sia un comando
 - 
Forma Normale Un Mé in forma normale se non puó piú essere ridotto, ovvero:- \(\nexists N \mid M \rightarrow N \implies M \nrightarrow\)
 Un termine in forma normale ci indica che la computazione é finita 
 - 
Corollario La forma normale di M, se esiste, é unica (a meno di α-conversioni).
 
- 
Strategie di Riduzione In alcuni casi é piú efficiente l’uno, in altri l’altro - 
Ordine Applicativo redex piú a sinistra e piú interno, linguaggi zelanti (\lambda x.x)((\lambda y.y)z) -> (\lambda x.x)z -> z. ---------- --------- applicare una funzione a un argomento significa prima valutare l’argomento poi sostituire nel corpo della funzione
 
 - 
Ordine Normale redex piú a sinistra e piú esterno, linguaggi pigri (\lambda x.x)((\lambda y.y)z) -> (\lambda y.y)z -> z----------------- --------- applicare una funzione a un argomento significa sostituire l’argomento nel corpo della funzione
- si posticipa la valutazione degli argomenti fino a che non é strettamente necessaria
 
 Ottimizzabile in caso di argomenti valutati piú volte - si memorizza il risultato parziale, in modo da non doverlo ricalcolare multiple volte
- questo é sicuro se il linguaggio é puro
- molto delicato da utilizzare in contesti diversi
- simile alla tecnica di memoizzazione nella Programmazione Dinamica
 
 
- applicare una funzione a un argomento significa sostituire l’argomento nel corpo della funzione
 - 
Teorema Normalizzazione Se \(M \Leftrightarrow N\) é normale, allora c’é una riduzione in ordine nomale \(M \Rightarrow N\) - se la forma normale di un’espressione esiste, la posso trovare riducendo l’espressione in ordine normale
- in un numero finito di passi
 
- questa proprietá non vale per l’ordine applicativo
- potrebbe finire in un loop nel cercare di risolvere subito gli argomenti
 
 
- se la forma normale di un’espressione esiste, la posso trovare riducendo l’espressione in ordine normale
 
- 
Programmare nel λ-calcolo
- 
Booleani TRUE = \lambda x.\lambda y.xFALSE = \lambda x.\lambda y.yIF = \lambda z.zAND = \lambda x.\lambda y.IF x y FALSEOR = \lambda x.\lambda y.IF x TRUE yNOT = \lambda x.\lambda y.IF x FALSE TRUEL’ordine applicativo non puó essere utilizzato nel caso di questo IF- questo perché nel caso del Falsel’elemento piú interno é quello che non andrebbe valutato, sprecando computazione per valutarlo inutilmente
 
- questo perché nel caso del 
- 
Coppie PAIR = \lambda x . \lambda y . \lambda z . z x yFST = \lambda p . p TRUESND = \lambda p . p FALSE
- 
Naturali Come iteratori: n = \lambda f . \lambda x . f^n xSUCC = \lambda a . \lambda f . \lambda x . a f (f x)ADD = \lambda a . \lambda b . b SUCC aMUL = \lambda a . \lambda b . b (ADD a) 0EXP = \lambda a . \lambda b . b (MUL a ) 1Il predecessore é piú complesso - idea: applicare nvolte una funzione che calcolancoppie, questa n-esima coppia nella prima componente avrán-1
 ISZERO = \lambda a . a (\lambda x . FALSE) TRUEFACT = \lambda a . IF (ISZERO a) 1 (MUL a (FACT (PRED a)))- non é una definizione in senso stretto, compare il nome della funzione anche a destra
 Da questa scrittura si evince che FACTé in forma- \(x = F(x)\)
- FACT = AUX FACT
 Che é la definizione di punto fisso della funzione FDefiniamo allora l’operatore di punto fisso:FIX = \lambda f . (\lambda x . f (x x)) (\lambda x . f (x x))alloraFACT = FIX AUX
- idea: applicare 
- 
Estendere il calcolo Per ragioni di efficienza ogni linguaggio di programmazione basato sul λ-calcolo fornisce dati nativi (numeri, booleani, caratteri, …) - questo peró permette di espressioni sintatticamente corrette ma prive di significato
 - 
Sistema di tipi Il problema é indecibile, vanno quindi previste delle approssimazioni conservative nello sviluppo di un sistema di tipi - 
Giudizio di Tipo - \(\vdash M :\: t\)
- Mé ben tipato e ha tipo- tnel contesto Γ
 
 
- \(\vdash M :\: t\)
 - 
Proprietá - Lemma Subject Reduction
- \(\Gamma \vdash M : \: t \land M \rightarrow N \implies \Gamma \vdash N : \: t\)
 
- Teorema Progresso
- \(\vdash M : \: t \land M \Rightarrow N \not\rightarrow \: \implies N \mbox{ é un valore}\)
- quindi una costante o una astrazione
 
 
- \(\vdash M : \: t \land M \Rightarrow N \not\rightarrow \: \implies N \mbox{ é un valore}\)
 
- Lemma Subject Reduction
 
- 
 
Algoritmo di Inferenza
- 
Fase di Costruzione dell’albero sintattico L’albero corrispondente al termine \(M\) é \(T[M]\) Casi: - variabile
- costante
- lambda astrazione
- si estende a destra
 
- applicazione
- associativa a sinistra
 
- if then else
 
- 
Fase dell’Annotazione dell’albero sintattico e della generazione dei vincoli Visita dal basso verso l’alto, a partire dalle foglie - variabile di tipo
- \(\text{TVar} = \{\alpha,\beta,\gamma, \cdots\}\)
 
- espressione di tipo
- \(\tau , \sigma := \begin{cases}\alpha \\ \mbox{Bool} \\ \tau \rightarrow \sigma \end{cases}\)
 
- vincolo
- \(\tau = \sigma\)
 
 Annotazioni: - variabile - \(\alpha\)
- nuova variabile di tipo, stessa per tutte occorrenze
 
- costante - \(\mbox{Bool}\)
- lambda - \(\alpha \rightarrow \tau \qquad \tau\)
- \(\alpha\) é nuova o la stessa di una occorrenza precedente della stessa variabile nel corpo
 
- applicazione - \(\alpha \;\mid\; \tau \qquad \sigma\)
- \(\alpha\) é nuova, generato il vincolo \(\tau = \sigma \rightarrow \alpha\)
 
- applicazione - \(\tau_{2}\;\mid\; \tau_{1}\rightarrow\tau_{2}\qquad\sigma\)
- ottimizzazione, generato il vincolo \(\tau_{1} = \sigma\)
 
- if then else - \(\tau_{2} \;\mid\; \tau_{1} \qquad\tau_{2} \qquad \tau_{3}\)
- generati i vincoli
- \(\tau_{1}=\mbox{Bool}\)
- \(\tau_{2} = \tau_{3}\)
 
 
- generati i vincoli
 
- variabile di tipo
- 
Fase di Risoluzione dei vincoli Si parte da un sistema ottenuto dalla fase precedente della forma: \(\{\tau_{i} = \sigma_{i}\}_{1\le i \le n}\) Si determina se questo ammette una soluzione - cerchiamo la soluzione piú generale
 Si procede agendo per sostituzioni - \(\theta(\tau)\) sostituendo in \(\tau\) tutte le \(\alpha\) con \(\theta(\alpha)\)
 Dove \(\theta\) é detta soluzione (o unificatore) del sistema se \(\forall i : 1\le i \le n \implies \theta(\tau_{i}) = \theta(\sigma_{i})\) L’algoritmo: - \(\tau = \tau\)
- eliminare il vincolo
 
- \(\tau = \alpha \land \tau\)  non é una variabile
- rimpiazzare il vincolo con \(\alpha = \tau\)
 
- \(\tau \rightarrow \tau^{'} = \sigma \rightarrow \sigma^{'}\)
- rimpiazzare il vincolo con \(\tau = \sigma \land \tau^{'}=\sigma^{'}\)
 
- \(\tau \rightarrow \sigma = \text{Bool} \lor \text{Bool} = \tau \rightarrow \sigma\)
- l’algoritmo fallisce con errore di tipo
 
- \(\alpha = \tau\)
- \(\alpha \neq \tau\) ma \(\alpha\) compare in \(\tau\)
- l’algoritmo fallisce con occur check
 
- \(\alpha\) non compare in \(\tau\), \(\alpha\) compare altrove
- sostituire \(\alpha\) con \(\tau\) in tutti gli altri vincoli (\(\alpha = \tau\) rimane)
 
 
- \(\alpha \neq \tau\) ma \(\alpha\) compare in \(\tau\)
- nessuna trasformazione applicabile
- l’algoritmo ha successo
 
 
- 
Estensioni Costanti: - False, True
- numeri interi
- numeri float
 Aggiungiamo i tipi corrispondenti. Le fasi 1 e 2 non hanno variazioni. La fase 3 fallisce se c’é un vincolo: - \(\tau \rightarrow \sigma = \text{Int}\)
- \(\text{Int} = \tau \rightarrow \sigma\)
- \(\text{Int} = \text{Bool}\)
 Aggiungendo le liste: \(c \in \{\cdots, [\:] ,(:)\}\) Tipi: - \((:) : : \alpha \rightarrow [\alpha] \rightarrow [\alpha]\)
- \([\:] : : [\alpha]\)
 La fase 1 non varia. La fase 2: - ogni occorrenza di un costruttore fa uso di nuove variabili di tipo
- questo vale per qualsiasi funzione polimorfa
 
 La fase 3: - i vincoli \([\tau] = [\sigma]\) si rimpiazza con \(\tau = \sigma\)
- fallisce per vincoli
- \([\tau] = \text{Bool}\)
- \(\text{Bool} = [\tau]\)
- \([\tau] = \sigma_{1} \rightarrow \sigma_{2}\)
- \(\alpha = [\alpha]\) - occur check
- il tipo contiene se stesso ed é contenuto da se stesso, errore
 
 
 Stesse considerazione valgono per le coppie: \(c \in \{\cdots,(\:,)\}\) Tipo: - \((\:,) : : \alpha \rightarrow \beta \rightarrow (\alpha,\beta)\)
 Le costanti includono le funzioni di libreria: \(c \in \{\cdots, \text{id}, \text{head}, \text{tail}, \cdots\}\) Per la ricorsione: \(f = M\) La fase 1: - \(f\) é trattato come una variabile
 La fase 2: - \(f\) é trattato come una variabile
- alla fine dell’annotazione generare il vincolo \(\alpha = \tau\)
- \(\alpha\) variabile associata a \(f\)
- \(\tau\) annotazione associata a \(M\)
 
 
Correttezza
Le dimostrazioni sono semplificate dal fatto che la funzione non mostra il suo stato al programmatore, tutto si trova nella definizione della funzione.
Gli approcci possibili sono:
- test
- non esaustiva
- puó dimostrare la presenza di un problema ma non la sua assenza
 
- piú facile
 
- non esaustiva
- dimostrazione
- esaustiva
- difficile, soprattutto se il linguaggio é imperativo
 
Relazioni totali
foo :: Int -> Int -> Int -> Int
Essendo l’ordine tra numeri totale é possibile considerare un numero finito di casi complessivamente esaustivi
Induzione
Il principio di induzione permette di dimostrare una proprietá per un insieme infinito di casi.
- Si dimostra il caso base
- Si dimostra il caso induttivo
- assumendo che a proprietá sia vera per il caso precedente \((n-1)\)
 
- 
Principio di Induzione Forte \((\forall m < n : P (m)) \implies P(n)\: \forall n \in \mathbb{N}\) 
- 
Principio di Induzione sulle liste finite Ogni lista é costruita a partire dalla lista vuota e un numero finito di applicazioni del costruttore : o cons- \(P([])\)
- \(P(xs) \implies P(x : xs) \forall x \land xs\)
 Come per il principio di induzione sui naturali é possibile generalizzare a tutte le liste piú corte di quella considerata per dimostrare il caso induttivo 
Estensionalitá
\begin{align*}
(.)\: f \: id &= f\: \\
(.)\: f\: id\: x &= f\: x
\end{align*}
Si applicano funzioni diverse ad uno stesso argomento arbitrario \(x\) e si dimostra che il risultato non cambia.
Legge di Fusione
Se
\begin{align*}
f\: a &= b \\
f\: (g\: x\: y)
&= h\: x (f\:y)
\end{align*}
Allora
\begin{align*} f\:.\:\text{foldr}\:g\:a = \text{foldr}\:h\:b \end{align*}
Stream
O liste infinite
- Haskellne gestisce una parte finita grazie alla lazyness
La bisimulazione serve per stabilire delle relazioni tra liste infinite.
- una tecnica indiretta per egualiare stream sintatticamente diversi
List Comprehension
\[S = \{2 \cdot x \mid x \in N , x \le 10\}\]
l = [x*2 | x <- [1..10]]
m = [x | x <- [50..100],
         x 'mod' 7 == 3] -- filters
xs = [(i,j) | i <- (1,2),
              j <- (1..4)]
Sintassi ispirata alla definizione intensionale di insiemi.
Liste infinite in Haskell
In Haskell
ones::[Integer]
ones = 1 : ones
ones = 1 : [1 | _ <- ones]
-- fibonacci
fibs::[Integer]
fibs = 1: 1: zipWith(+) fibs tail fibs
fibs = 1: 1: [x+y | x <- fibs, y <- tail fibs] -- wrong
fibs = 1: 1: [x+y | x <- fibs, y <- [head(tail fibs)] -- still wrong
fibs = 1: 1: [x+y | x:y:xs <- tails fibs] -- Data.List
La seconda versione di fibs non funziona in quanto con 2 generatori la list comprehension agisce come un for sui due, elemento per elemento; la seconda lista in y non finisce di ciclare e considera solo il primo x (1)
La terza rimane sbagliata a causa della prioritá del generatore, che precede tutto ( <- )
L’ultima versione risolve il problema con il pattern matching e la funzione tails in Data.List
Formalmente
Stream definiti su \(A\): \[A^w = \{\sigma \mid \sigma : \{0,1,2,\cdots\}\rightarrow A \}\]
- liste enumerate
- \(A\), il codominio, puó essere qualsiasi insieme
- uno Stream é infinito per definizione, il codominio di una funzione non puó essere \(\emptyset\)
- \(A\) non puó essere \(\emptyset\)
 
\(\sigma(0)\)
- head
\(\sigma’(n) = \sigma(n+1)\)
- derivata prima
- tail
\(\sigma(n)\)
- ennesimo di \(\sigma\)
\(\sigma^{(0)} = \sigma\)
- derivata zeresima
\(\sigma^{(k+1)}=(\sigma^{(k)})'\)
- induttivamente; derivata di ordine superiore
\[\sigma= a : \tau = (a,\tau(0),\tau(1),\cdots) \]
- \[\sigma(0) = a \qquad \sigma’ = \tau\]
Laboratorio
Haskel
Storia
- \(\lambda\) calcolo
- Alonzo Church
- calcolare con le funzioni, cosi' come con in numeri
- tutto e' una funzione con 1 IN e 1 OUT
- funzioni anonime
- identita'
- \(\lambda x,x\)
 
 
- identita'
 
- funzioni anonime
 
- Haskell Curry
- currying
 
 
- Alonzo Church
- LISP - anni ‘50
- John McCarthy
- elaborazione informazione non-numerica/simbolica
- LISP = List Processor
- cons e map nascono qui
 
- primo garbage collector
 
 
- John McCarthy
- ML
- SASL, KRC, Miranda
- linguaggi lazy con valutazione solo al momento della richiesta della funzione
- SASL introduce guardie e currying
 
- Haskell - anni ‘90
- linguaggio lazy, standardizzato
- separazione tra puro e impuro
- monadi
 
- overloading
- grosso impatto sul calcolo parallelo
 
Casi di Studio
- 
Contatore accessi Web Relazione biunivoca tra IP e utenti unici in accesso Java public static int counter(InputStream stream) { Scanner scanner = new Scanner(stream); Set<String> clients = new HashSet<>(); while (scanner.hasNextLine()) { String line = scanner.nextLine(); String ip = line.substring(0, line.indexOf(' ') + 1); clients.add(ip); } return clients.size(); }Bash cut -d' ' -f1 | sort -u | wc -lHaskell import Data.List (nub); counter :: String -> Int counter = length . nub . map (\line -> takeWhile (/= ' ') line) . linesJava 8 public static long counter(InputStream stream) { InputStreamReader reader = new InputStreamReader(stream); return new BufferedReader(reader) .lines() .map(line -> line.substring(0, line.indexOf(' ') + 1)) .distinct() .count(); }
- 
Fibonacci Logaritmico type Matrice = (Integer, Integer, Integer, Integer) mul :: Matrice -> Matrice -> Matrice mul (a₁₁, a₁₂, a₂₁, a₂₂) (b₁₁, b₁₂, b₂₁, b₂₂) = (a₁₁ * b₁₁ + a₁₂ * b₂₁, a₁₁ * b₁₂ + a₁₂ * b₂₂, a₂₁ * b₁₁ + a₂₂ * b₂₁, a₂₁ * b₁₂ + a₂₂ * b₂₂) pow :: Matrice -> Int -> Matrice pow a k | k == 0 = (1, 0, 0, 1) k `mod` 2 == 0 = b `mul` b otherwise = a `mul` b `mul` b where b = a `pow` (k `div` 2) fibonacci :: Int -> Integer fibonacci k = risultato where (_, risultato, _, _) = (1, 1, 1, 0) `pow` k 
- 
Ordinamento insertSort :: [Int] -> [Int] insertSort [] = [] insertSort (x : xs) = insert x (insertSort xs) where insert x [] = [x] insert x (y : ys) | x <= y = x : y : ys otherwise = y : insert x ys split :: [Int] -> ([Int], [Int]) split [] = ([], []) split [x] = ([x], []) split (x : y : xs) = (x : ys, y : zs) where (ys, zs) = split xs merge :: [Int] -> [Int] -> [Int] merge [] ys = ys merge xs [] = xs merge (x : xs) (y : ys) | x <= y = x : merge xs (y : ys) otherwise = y : merge (x : xs) ys mergeSort :: [Int] -> [Int] mergeSort [] = [] mergeSort [x] = [x] mergeSort xs = merge (mergeSort ys) (mergeSort zs) where (ys, zs) = split xs 
- 
Java Virtual Mini-Machine - PUSH v
- LOAD x
- STORE x
- OP f
- IF R l
- RETURN
 type Value = Int type Var = Int – indice di una variabile type Stack = [Value] – con : inseriamo in testa – estraiamo con pattern matching type Frame = [Value] – qui invece dobbiamo accedere – in posizioni arbitrarie Per i Framedefiniamo:load :: Var -> Frame -> Value load _ [] = 0 load 0 (v : ) = v load n (_ : vs) = load (n-1) vs store :: Var -> Value -> Frame -> Frame store 0 v [] = [v] store 0 v (_ : vs) = v : vs store n v [] = 0 : store (n-1) v [] – inizializzazione a 0 store n v (w : vs) = w : store (n-1) v vs Le Istruzioni possiamo pensarle come sottoclassi di una classe astratta Istruzionedata Instruction = PUSH Value LOAD Var STORE Var OP (Value -> Value -> Value) IF (Value -> Value -> Bool) Code RETURN type Code = [Instruction] Queste definizioni algebriche sono simili a produzioni di una grammatica Permettono l’espressione di codice di questa forma: fibonacci :: Code fibonacci = init where init = PUSH 0 : STORE m : PUSH 1 : STORE n : loop loop = LOAD k : PUSH 0 : IF (==) fine : LOAD n : LOAD n : LOAD m : OP (+) : STORE n : STORE m : LOAD k : PUSH 1 : OP (-) : STORE k : loop fine = LOAD m : RETURN : [] k = 0 m = 1 n = 2 L’implementazione é completata con un interprete: run :: Code -> Frame -> Value run = aux [] where aux :: Stack -> Code -> Frame -> Value aux (v : []) (RETURN : ) _ = v aux vs (PUSH v : is) fr = aux (v : vs) is fr aux vs (LOAD x : is) fr = aux (load x fr : vs) is fr aux (v : vs) (STORE x : is) fr = aux vs is (store x v fr) aux (w : v : vs) (OP f : is) fr = aux (f v w : vs) is fr aux (w : v : vs) (IF p is : ) fr | p v w = aux vs is fr aux ( : _ : vs) (IF _ _ : is) fr = aux vs is fr 
Caratteristiche Linguaggio
- 
Guardie Introducono delle condizioni, alternativa al piú operazionale if...then...elseassoluto :: Int -> Int assoluto n | n >= 0 = n | n < 0 = negate nNel caso che i casi siano esaustivi l’ultimo identificatore puó essere otherwiseL’ordine delle guardie é significativo, sará scelta la prima guardia il cui valore sia valutatoTrue
- 
Ricorsione Non esistono loop non esistendo la memoria, e quindi variabili su cui fare iterazione. e É quindi necessario utilizzare le definizioni ricorsive: fattoriale :: Int -> Int fattoriale n | n == 0 = 1 -- supponendo n >= 0 | otherwise = n * fattoriale (n - 1)Si possono specificare piú equazioni, semplificando il codice fattoriale :: Int -> Int fattoriale 0 = 1 -- lo 0 fa riferimento al parametro utilizzato fattoriale n = n * fattoriale (n - 1)Altro esempio classico, la sequenza di fibonacci fibonacci :: Int -> Int fibonacci 0 = 0 fibonacci 1 = 1 fibonacci n = fibonacci (n - 1) + fibonacci (n - 2)Anche usando questa forma Haskell valuta le funzioni dall’alto verso il basso, nell’ordine. - i pattern piú generali vanno piú in basso, Haskellin caso emette unWarningriguardo la ridondanza dei match non raggiungibili
 - 
Dall’iterazione alla ricorsione Esistono algoritmi piú efficienti in forma iterativa - fibonacciapplicato ricorsivamente ha una complessitá \(n^{2}\)
- una versione iterativa in un linguaggio imperativo ha complessitá \(n\)
 É possibile riprodurre anche in Haskelll’iterazione con un metodo meccanico- inserire le variabili che vengono modificate nell’iterazione all’interno di una funzione ricorsiva che simula il ciclo
 public static int fibonacci(int k) { assert k >= 0; int m = 0; int n = 1; while (k > 0) { n = n + m; m = n - m; k = k - 1; } return m; }fibonacciAux :: Integer -> Integer -> Int -> Integer fibonacciAux m _ 0 = m fibonacciAux m n k = fibonacciAux n (m + n) (k - 1) fibonacci :: Int -> Integer fibonacciAux 0 1 -- applicazione parziale fibonacci :: Int -> Integer fibonacci = aux 0 1 where aux m _ 0 = m aux m n k = aux n (m + n) (k - 1)Una serie di chiamate ricorsive del genere consumerebbe memoria aumentando la dimensione dello stack dei frame? Meno efficiente del corrispettivo imperativo? No, il compilatore Haskellricicla il vecchio frame delle funzione in quanto vede che i vecchi valori non sono utilizzati dopo la prima applicazione- quando la funzione é ricorsiva in coda, ovvero la chiamata ricorsiva é l’ultima cosa fatta dalla funzione
 
 
- i pattern piú generali vanno piú in basso, 
- 
Funzioni Anonime λ-Astrazioni (\x -> x+1) 2 (\x -> x >= 0) 2In Haskell, si dice sezione un’espressione racchiusa tra parentesi in cui un operatore binario viene applicato a uno solo dei suoi due argomenti. (1 +) ('mod' 2)
- 
Currying addizione :: Int -> Int -> Int addizione x y = x + y addizione = \x -> \y -> x + y -- espandendo in lambda astrazioni é piú chiaro il tipoDa qui emerge l’associativitá a destra del tipo freccia: (Int -> (Int -> Int))Questo é speculare alla composizione in lambda calcolo Si puo’ convertire tra tipi numerici utilizzando: - fromIntegral
- truncate
- round
 
- 
Coppie e Tuple E’ sufficiente circondare gli elementi con parentesi tonde 
- 
Liste Sequenza omogenea di elementi, che hanno quindi lo stesso tipo La sintassi utilizza le parentesi quadre. - [1..]lista con tutti i numeri interi da 1 in avanti- possibile perche' il linguaggio e' lazy
 
 Ogni lista puo' essere contruita a partire da due costruttori canonici - X : L- utilizzando cons
 
- 1 : 2 : 3 : []- cons e lista vuota
 
 Esiste funzione di concatenazione di liste - ++- non modifica le liste di partenza, ne crea una nuova
- un linguaggio puro come Haskellnon modifica strutture esistenti
 
 
Tipi e Classi
Haskell e' un linguaggio fortemente tipato
- 
Tipi primitivi - Intnumeri interi a precisione finita
- Integernumeri interi a precisione arbitraria
- Floatnumeri in virgola mobile a precisione singola
- Doublenumeri in virgola mobile a precisione doppia
- Boolbooleani Il comando- :typedi GHCi interroga- Haskellsul tipo inferito ad una espressione Si puo' scrivere il tipo di un valore affianco ad esso con la sintassi- :: Int- non e' una conversione di tipo, e' solo una annotazione utile al compilatore
 
 
- 
map map :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x : xs) = f x : map f xs 
- 
filter filter :: (a -> bool) -> [a] -> [b] filter _ [] = [] filter p (x : xs) | p x = x : filter p xs otherwise = filter p xs 
- 
fold foldr :: (a -> b -> b) -> b -> [a] -> b foldr _ x [] = x foldr f x (y : ys) = f y (foldr f x ys) – come fosse associativo a destra 
Java8
Introduce Lambda Espressioni e Stream (ovvero liste infinite)
- introdotti durante la revisione delle Collection- nella revisione é stato utilizzato il paradigma funzionale
 
- Queste Streamsono lazy e sono molto piú ad alto livello degli stream a livello diOS
Java é un linguaggio procedurale, dove viene manipolata la mamoria. Al contrario di linguaggi dichiarativi dove il programmatore si occupa del cosa, non del come (SQL, Haskell)
- la lazyness é perfetta quando si trattano strutture infinite, permette infatti di non valutare tutto l’argomento prima di applicare funzioni
Polimorfismo
class B extends class A \(\implies B <: A\)
- Bé sottotipo e puó essere utilizzato in qualsiasi contesto in cui- Apuó essere utilizzato
Il bytecode Java non é generico di per se (lo era), quindi a quel livello sono automaticamente introdotti cast dal compilatore
Il super viene legato a tempo di compilazione (statico) a differenza di this (dinamico)
Filters
Utilizzando un Design Pattern Strategy posso parametrizzare il predicato rispetto al filtro
- rendendolo polimorfo
- utilizzando una Interfaceper una classe-predicato che implementerá effettivamente iltest
- che poi é utilizzabile anche con le classi anonime al volo
Lambda
(parameters) -> expression
oppure
(parameters) -> { statements; }
(x:int) -> x
// or
(x:int) -> {return x;}
non é introdotto un nuovo costruttore di tipo, vengono riutilizzate le interfacce funzionali
- questa interfaccia ha un solo metodo astratto che corrisponde al body della \(\lambda\)
- esistono interfacce di questo tipo predefinite, ovviamente ne si puó definire
- @FunctionalInterface- Predicate<T>
- Comparator<T>
- Callable<T>
- Runnable<T>
- Function<T>
 
- Docs Java8