Lezione 1 - Imperativo o procedurale Corso — Programmazione Linguaggi di programmazione— Paradigmi di programmazione Marco Anisetti e-mail: [email protected]web: http://homes.di.unimi.it/anisetti/ Università degli Studi di Milano — Dipartimento di informatica
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Lezione 1 - Imperativo o procedurale
Corso — Programmazione
Linguaggi di programmazione— Paradigmi di programmazione
• Ha le sue radici nel lambda calcolo che Alonso Church ha
inventato negli anni 30
• Un programma è una funzione che viene valutata per
ottenere un risultato
• Il principio della programmazione funzionale pura è: ``il
valore di una espressione dipende solamente dai valori
delle sue sub-espressioni, se ne esistono''
• Quindi gli assegnamenti non sono considerati, dato che
possono cambiare valori alle variabili
• La programmazione funzionale pura è una
programmazione senza assegnamenti
• Non si considerano le tecniche di salvataggio dei valori
della macchina sottostante
• I cambi del valore di una variabile durante la valutazione
di una espressione sono considerati come degli ``side
effect''
• Esempi storici ML, Lisp, Scheme etc.
Programmazione funzionale(2)
• La potenza di questo paradigma stà nell'uso diretto di
funzioni e ricorsione
• Gestione dell'allocazione implicita
• Storage allocato se serve e deallocato se non serve più
(garbage collection)
• Funzioni come ``first-class values''. Le funzioni hanno lo
stesso status di ogni altro valore
• Una funzione può essere il valore di una espressione, può
essere passata come argomento e può essere messa in
una struttura dati o in una variabile, può essere restituita
come valore di ritorno da altre funzioni
Programmazione funzionale(2)
• Le funzioni possono avere dei nomi, come in ogni altro
linguaggio, o essere definite anonimamente (a volte
anche a run-time)
• Si basa sulle computazione di espressioni
log n: funzione logaritmica applicata a n2+4: funzione + applicata a 2 e 4Massimo tra x e y: if (x > y) then x else y (espressionicontenenti condizioni e definizione di funzioni)
Programmazione funzionale vs imperativa
• Il punto di forza della programmazione funzionale è a
mancanza di effetti collaterali delle funzioni
• Si ha una migliore verifica del codice e migliore
ottimizzazione
• Nella programmazione imperativa avviene il cambio dello
stato del programma attraverso assegnazioni
• Nella programmazione funzionale i valori non vengono
calcolati cambiando lo stato del programma ma
costruendo nuovi stati a partire dai precedenti
• Vedremo un esempio famoso per chiarire questo tipo di
concetto
• Non può per definizione dare luogo a bug a run-time. Al
momento della compilazione o della prima esecuzione o
funziona o non funziona (non esistono eccezioni)
Effetti collaterali di una funzione
• Una funzione ha un effetto collaterale quando modifica un
valore o uno stato al di fuori del proprio scopo
• Esempio: se modifico una variabile globale o una statica,
quando modifico il valore di un argomento, o scrivo su un
file o chiamo funzioni con effetti collaterali
• La programmazione imperativa vive degli effetti collaterali
delle funzioni
• La programmazione funzionale tende a limitarli (più
difficili le operazioni di input output)
Trasparenza referenziale(1)
• La trasparenza referenziale significa che una espressione
(anche una funzione quindi) può essere sostituita con il
suo valore
• Se considero la funzione f(sqr(2),sqr(2)) allora posso
evitare di calcolare due volta sqr(2) se ho la trasparenza
referenziale
• Per avere la trasparenza referenziale una funzione deve
essere senza effetti collaterali e deve essere puramente
funzionale (restituire sempre lo stesso output a fronte
degli stessi input)
Trasparenza referenziale(2)
• A volte si parla anche di high order functions quando una
funzione prende una o più funzioni come input e da come
output una funzione
• Esempio f(x)= x+4, la funzione high order doppia è
• I valori strutturati sono utili per ritagliare la nozione di
valore per una specifica applicazione
• In seguito specificheremo la strutturazione come il set di
valori ed i relativi set di operazioni
• Ci concentreremo sulle operazioni per la costruzione e
l'ispezione di elementi di un set
• Tipi base: se i valori sono atomici ovvero se i valori sono
trattati come un tuttuno senza struttura interna
Tipi: valori e espressioni(3)
• Operazioni sui tipi base: Non avendo struttura internal'unica operazione definita è quella di confronto peruguaglianza
Tecnicamente una operazione è una funzione, quindi peresempio il test di uguaglianza tra interi è una funzionedefinita su una coppia di interi a valori nei booleani.E' un elemento di tipo int ∗ int → bool
• Prodotto di tipi: Il prodotto di due tipi A ∗ B consistenelle coppie ordinate scritte come (a,b) con a ∈ A e b ∈ B
Il prodotto di n tipi è una tupla (n-upla)
• Projection functions: Sono associate alle coppie e
servono ad estrarre i due elementi
fun first(x,y) = x
fun second(x,y) = y
Tipi: Liste
• Una lista è una sequenza finita di elementi
• Un tipo A list consiste in tutte le liste di elementi dove
ogni elemento appartiene al tipo A
• Esempio: int list
• Gli elementi di una lista vengono elencati tra parentesi
quadrate e separati con la virgola ['rosso','blu','marrone']
• Il linguaggio funzionale ML prevede operazioni sulle liste
tipo: null(x) che ritorna true se è vuota e false altrimentila lista, hd(x) che ritorna l'elemento di testa, tl(x) cheritorna la lista tranne la testa eccetera
• Esiste anche l'operatore :: è associativo destro e costruiscela lista indicando testa e coda (1::[2,3])
Tipi: Funzioni
• Il tipo A→ B è l'insieme di tutte le funzioni da A a B
• Funzione totale: A→ B se è definita per ogni elemento di
A, quindi associa un elemento di B per ogni elemento di A
• A è il dominio e B è il range della funzione
• Funzione parziale: Non è definita per ogni elemento di A
Applicazione delle funzioni e dichiarazione
• L'applicazione dell'insieme A→ B prende una funzione f
definita da A→ B un elemento a di A e produce un
elemento b di B
• L'applicazione di una funzione f ad un argomento a è
scritta come f a (in ML)
• Abbiamo già visto di cosa son formate le funzioni nella
programmazione imperativa e come vengono usate e
dichiarate
• In programmazione funzionale si tratta solitamente di una
sintassi del tipo: fun <nome> <parametri formali> =
<corpo>
• Esempio: fun successore n = n+1
• L'uso della funzione chiamato applicazione è in forma
prefissa e segue la sintassi <nome> <parametri attuali>
• Esempio: successore (2+3)
Funzione ricorsiva
• Le funzioni ricorsive sono il sale della programmazione
funzionale
• Vale la definizione già vista
• Usiamo le funzioni viste sulle liste per scrivere la funzione
che calcola la dimensione di una lista
• fun lenth(x) = if null(x) then 0 else 1+lenth(tl(x))
Innermost evaluation
• Consideriamo la applicazione <nome> <parametri
attuali>• Innermost:
Valuto l'espressione in <parametri attuali>, sostituisco ilrisultato per i formali nel corpo della funzioneValuto il corpoRitorno il risultato
• Nell'esempio di successore (2+3) calcolo 2+3 e lo
sostituisco a n parametro formale che vale quindi 5
• Ogni valutazione del corpo di una funzione si chiama
attivazione (analogamente al record di attivazione nello
stack)
• Innermost significa che in una serie di funzioni a cascata
si valuta dall'interno es f(g(d(x)))
• I parametri vengono valutati sempre anche se in quello
specifico caso non servono (analogo alla chiamata per
valore)
Selezione
• Il costrutto di selezione viene visto come la possibilità di
valutare selettivamente una parte di una espressione o un
altra
• if <condizione> then <expr1> else <expr2>
• Il valore di questo costrutto è il valore di <expr1> o di
<expr2> a seconda di <condizione> (non ci sono
assegnamenti!)
Outermost evaluation
• I parametri sono valutati solo quando serve
• Outermost:
Sostituzione dei formali con gli attuali nel corpo dellafunzioneValutazione del corpoRitorno del valore
• I parametri non sono valutati prima della sostituzione al
posto dei formali ma solo quando serve (da sinistra a
destra)
Esempio la funzione 91
• La funzione 91 è la seguente funzione:
• fun f(x) = if x > 100 then x-10 else f(f(x+11))
• Vediamo cosa succede per il caso outermost e innermost
per la valutazione di f(100)
Esempio innermost
f(100) = if 100>100 then 100-10 else f(f(100+11))
= f(f(100+11))
= f(f(111))
= f( if 111> 100 then 111-10 else f(f(111+11)) )
= f(111-10)
= f(101)
= if 101>100 then 101-10 else f(f(101+11))
= 101 – 10 = 91
Esempio outermost
f(100) = if 100>100 then 100-10 else f(f(100+11))
= f(f(100+11))
= if f(100+11)>100 then f(100+11)-10 else
f(f(f(100+11)+11))
valuto la prima f(100+11)
f(100+11) = if 100+11> 100 then 100+11-10 else
f(f(100+11+11))
= if 111>100 then 100+11-10 else f(f(100+11+11))
= 100+11-10
= 111-10 = 101
f(100)= if (101> 100) then f(100+11)-10 else
f(f(f(100+11)+11))
=f(100+11)-10
=91
Innermost vs. outermost
• Se esitesse una implementazione della tecnica outermost
che riconoscesse il fatto di aver già calcolato f(100+11)
allora si eviterebbe di fare molti calcoli in aggiunta
• In questo esempio una implementazione non ottimizzata
di outermost risulta non efficiente
• Esempio: consideriamo la funzione fun or(x,y) = if xthen true else y
Se ci fosse la chiamata or (A,B) nel caso innermost dovreivalutare prima A e B poi passarli come parametri formaliNel caso or(true,B) con B che non termina porta alla nonterminazioni nel caso outermost la sostituzione noncomporta la valutazione quindi verrebbe if true then trueelse BQuindi B non viene valutato e il programma terminaComportamento simile allo short circuit
Operatori short circuit
• In alcuni linguaggi tipo il funzionale ML esistono operatori
che espressamente fanno uso di questo concetto
• andalso: A andalso B è falsa se A è falso e vera se A e B
sono veri
• orelse: A orelse B è vera se A è vera e falsa se A e B
sono falsi
Scopo lessicale
• Una occorrenza locale o bound di una variabile può essere
rinominata senza cambiare il significato del programma
• Queste due funzioni sono equivalenti fun successor(x) = x
+ 1 fun successor(n) = n + 1
• Le regole per definire lo scopo lessicale usano il codice
vicino alla dichiarazione della funzione per determinare il
contesto dei nomi non locali
• Si tratta di controlli statici
• Esempio fun add(x) = x + y; la y non è un parametro il
suo valore deve essere determinato dal contesto
Scopo lessicale: val bindings
• Consideriamo la seguente forma di espressione con l'usilio
del let
let val x=E1 in E2 end
• L'occorrenza della x alla destra del let si chiama binding di
x
• Tutte le occorrenza di x in E2 si dicono nello scopo di
questo binding
• Esempio: let val x=2 in let val x=x+1 in x*x end end
Scopo lessicale: fun bindings
• Consideriamo: let fun f(x)=E1 in E2 end
• Il binding del parametro formale x è visibile solo nelle
occorrenze di x in E1
• Il binding della funzione f è visibile sia in E1 ( sarebbe
ricorsione) che in E2
Controllo dei tipi
• Come nel caso di un linguaggio imperativo si tratta di un
insieme di regole per associare un tipo ad una espressione
• Inferenza di tipo
• Equivalenza di tipi
• Overloading: un simbolo è overloaded se ha diversi
significati a seconda del contesto (a volte difficile da
risolvere e richiede tipi espliciti)
• Coercion: conversione implicita da un tipo ad un altro
data dal linguaggio
• Polimorfismo, tipi parametrici es le operazioni sulle liste di
elementi di qualsiasi tipo basta che siano liste (operatori
hd e tl visti in precedenza)
Modellare la semantica del binding (1)
• Analogamente alla semantica operazionale con la nozione
di stato posso specificare meglio la nozione di binding
usando una forma premessa/conseguenza più specifica
• E ::= let x = E1 in E2
• Consideriamo due operazione sul contesto o environment
env che mappa un valore su una variabile (analogo al
concetto di scopo)
• bind(x,v,env) è un nuovo contesto che mappa la variabilex al valore v
• lookup(x,env) è il valore della variabile x nel contesto env
Modellare la semantica del binding (2)
• env → E : v significa che nel contesto env l'espressione E
ha valore v
• env → x : lookup(x,env) significa che nel contesto env la
variabile x ha valore lookup(x,env)
• La regola per l'espressione let si può scrivere come:env→E1:v1 bind(x,v1,env)→E2:v2
env→let x=E1inE2:v2
• Ovvero il valore dell'espressione let x = E1 in E2 è v2 nelcontesto env se:
E1 vale v1 nel contesto envE2 vale v2 nel contesto dove x è bound a v1
Grammatica ad attributi per il let
• L'attributo val denota il valore, env per denotare il
contesto che determina il mapping sul valore
E.val := E2.val ovvero il valore di E è il valore di E2
E1.env := E.env ovvero il contesto di binding di E1 è lo
stesso contesto di E
E2.env := bind(x,E1.val,env) ovvero in E2 x è associata al
valore di E1
Approfondimenti: ricorsione(1)
• Ricorsione lineare: se una attivazione di f(a) può
attivare al più una singola attivazione di f
• Esempio: fun fattoriale(n)= if n=0 then 1 else
n*fattoriale(n-1)
• Fasi della ricorsione:
• Winding: iniziano le attivazioni
• Unwinding: le attivazioni rilasciano il controllo (simile al
LIFO)
Approfondimenti: ricorsione(2)
• Ricorsione tail: se restituisce un valore (senza richiedere
ricorsione) oppure il risultato di una attivazione ricorsiva
• Esempio: fun g(n,a)=if n=0 then a else g(n-1, n*a)
• La ricorsione viene realizzata (crea valore) tutta nella fase
di winding
Conclusioni
• Ci ha permesso di riflettere su molti concetti già visti ed
applicarli ad un paradigma differente
• Apre la mente verso strategie di programmazione più
astratte e lontane dal modo di operare della macchina
• Avvicina a concetti di programmazione ad oggetti moderni
• Si trova in tanti linguaggi attuali che generalmente
ibridano paradigmi differenti (es Javascript Ruby etc)
• Permette di risolvere in maniera molto elegante certi tipi
di problemi e semplifica il debug
• Riflessioni su alcuni esempi di analisi semantica applicata
a costrutti funzionali
Lezione 4 - Logico e orientato agli oggetti
Corso — Programmazione
Linguaggi di programmazione— Paradigmi di programmazione
• Procedurali: istruzioni che indicano al calcolatore come
svolgere un determinato compito. Dati - istruzioni -
risultato
• Dichiarativi: indicano cosa deve essere fatto ma non
come. Lasciano al calcolatore la scelta del come farlo
Programmazione logica(1)
• Descrivere la conoscenza che si ha sul problema piuttosto
che il procedimento che genera la sua soluzione
• Occorre rappresentare bene le entità in gioco in relazione
all'applicazione usando un linguaggio logico
• Il problema è descritto da oggetti o entità, da relazioni tra
loro e da proposizioni che esprimono proprietà logiche
sulle relazioni
• Problema descritto come un insieme di formule logiche
• Sfrutta un linguaggio di natura dichiarativa
• Un programma in questo paradigma è un insieme di fatti e
regole e la sua esecuzione equivale alla realizzazione di
una dimostrazione (deduzione).
• Si forniscono i fatti (informazioni vere) le regole che
compongono i fatti o risultati di altre regole
• Si interroga il programma per trovare la soluzione
Programmazione logica(2)
• Programmazione classica: Algoritmi + Strutture dati =Programmi
Dipendenza tra dati e algoritmo
• Programmazione logica: Algoritmo = Logica(fatti +regole) + Controllo (risoluzione)
Dati e controllo indipendenti
• La programmazione logica viene di solito affiancata allo
studio del linguaggio Prolog
Programmazione logica(3)
Fatti:
Every psychiatrist is a person.
Every person he analyzes is sick.
Jacques is a psychiatrist in Marseille.
Interrogazioni:
Is Jacques a person?
Where is Jacques?
Is Jacques sick?
Fondamenti della programmazione ad
oggetti: Raggruppare dati e operazioni
• Scomposizione di problemi in sottoproblemi
• Astrazione: procedurale, modulare, dei dati, di oggetti
• Astrazione procedurale: abbiamo già visto è una delle
prime ad essere state applicate. Permette di concentrarsi
su una sezione del codice alla volta• Astrazione modulare: è stata sfruttata per molto tempoprima di concretizzarsi in un paradigma diprogrammazione. Abbiamo visto le basi della modularitàassociata alla progettazione.
Collezione di dati e procedure che agiscono sui dati. E'meglio organizzare moduli attorno ai dati che attorno alleazioni sui dati (astrazione dei dati)
• Astrazione dei dati: è un tipo definito dal
programmatore (stack, albero ecc.).
• Astrazione degli oggetti: è il paradigma di
programmazione orientato agli oggetti
Linguaggio e astrazione
• Supporto del linguaggio: L'astrazione richiede che il
linguaggio di programmazione permetta di nascondere
delle informazioni rendendo il programma più semplice da
leggere
• Un linguaggio supporta uno stile di programmazione se lo
stile può essere espresso, controllato ed implementato in
modo efficiente
• Un linguaggio permette uno stile di programmazione se
posso con qualche trucco arrivare a programmare con i
dettami dello stile ma non ho il pieno supporto del
linguaggio
• La possibilità di nascondere informazioni supporta varie
forme di astrazione e di solito si realizza con la definizione
della visibilità dei nomi
• Alcuni linguaggi supportano nativamente la modularità
come ad esempio il Pascal (unit - interface -
implementation)
Astrazione procedurale
• Nel momento in cui una procedura è stata definita, la sua
implementazione può essere astratta (non importa più il
suo corpo ma quello che fa e l'interfaccia).
• Comportamento di una procedura: cosa fa vista dal di
fuori
• Implementazione di una procedura: nasconde i
dettagli implementativi di come il comportamento è
ottenuto
• Prendiamo ad esempio un parser che fa anche la
valutazione. Sappiamo che è costituito da uno scanner
(analisi lessicale e divisione in token) e da un parser con
valutazione (valutazione grammaticale e del valore
semantico)
• Il dividere in procedure aiuta la progettazione, i moduli
sono ancora più utili dato che scanner e parser lavorano
su dei dati
Astrazione modulare
• Un modulo è una collezione di dichiarazioni che includono
variabili e procedure
• Il modulo agisce come una black-box con cui il resto del
programma interagisce attraverso la sua interfaccia
• Una interfaccia è un sottoinsieme delle dichiarazioni del
modulo
• Le interfaccie e le implementazioni del modulo possono
considerarsi private o pubbliche
• Ruolo: modulo organizzato attorno ai dati. Il ruolo è
generale non specifico di procedure che sono
implementate nel modulo
• Interfaccia: se il ruolo è chiaro si definisce meglio la sua
interfaccia. Le procedure di interfaccia determinano il
comportamento del modulo per il resto del programma
• Implementazione: Nasconde nelle parti private del
modulo le decisioni (variabili e procedure) che non sono di
interesse per il resto del programma
Tipi - classi - oggetti
• Il programma manipola dei tipi strutturati
• Una classe corrisponde ad uno di questi tipi
• La classe è l'abbreviazione di classe di oggetti
• Un oggetto è una entità che vive a run-time con dei dati
istanziati su cui le operazioni possono agire
• Uno stack è un oggetto i cui dati corrispondono con il
contenuto della stack stesso
Procedure - moduli - classi
• Servono a bisogni differenti e possono essere usati in
combinazione
• Procedure: sono richieste per implementare una
operazione di un modulo o di una classe
• Modulo: può essere usato per partizionare staticamente
il codice di un programma organizzato in classi
• Le differenze non sono nelle attività ma in come sono
organizzate
• Procedure: Definiscono ogni dettaglio ogni
rappresentazione è nota tramite il programma.
• Moduli: Nasconde la rappresentazione, esponendo le
operazioni pubbliche. Il vantaggio rispetto alla procedura
è che la rappresentazione è nascosta e l'accesso è
controllato.
• Classi: Come per i moduli la rappresentazione è nascosta
l'accesso è controllato. Gli oggetti vengono inizializzati
alla creazione
Classe ed eliminazione dei doppi(1)
• Rivediamo l'esempio dell'algoritmo della eliminazione dei
doppi considerando come elementi da confrontare delle
istanze di una classe Entry così definita:
Class Entry
procedure read();
procedure write();
function endmarker():boolean;
function equal(Entry):boolean;
function copy(Entry):boolean;
constructor Entry;
destructor Entry;
Classe ed eliminazione dei doppi(2)
Entry a,b;
a.read();
while not a.endmarker() do
a.write();
do
b.read;
while a.equal(b)
a.copy(b)
endwhile
Programmazione orientata agli oggetti
• Approccio che si integra con quello procedurale ma la
progettazione è radicalmente differente
• Un programma è un insieme di oggetti (astrazioni della
realtà) dotati di proprietà (dati) e metodi (procedure) che
comunicano tramite scambio di messaggi.
• E' un paradigma tanto importante da essere stato
adottato da molti dei linguaggi più recenti che ibridano
diversi paradigmi
• Javascript: OO e funzionale, Ruby fortemente OO
(aggiungere e modificare metodi a run-time) e funzionale
• Python OO e funzionale, molto usato in scripting
• Uno tra i più usati linguaggi di programmazione ad oggetti
prima dell'avvento di Java è stato C++ che poteva essere
usato per scrivere in modalità procedurale o ad oggetti