IL MODELLO CLIENTE / SERVITORE
Servitore: un qualunque ente computazionale capace di nascondere la propria organizzazione interna presentando ai clienti una precisa interfaccia per lo scambio di informazioni. Cliente: qualunque ente in grado di invocare uno o più servitori per svolgere il proprio compito. CLIENTI E SERVITORI
SERVITORI Un servitore può essere passivo o attivo servire molti clienti oppure costituire la risorsa privata di uno specifico cliente –in particolare: può servire un cliente alla volta, in sequenza, oppure più clienti per volta, in parallelo trasformarsi a sua volta in cliente, invocando altri servitori o anche se stesso.
COMUNICAZIONE CLIENTE / SERVITORE Lo scambio di informazioni tra un cliente e un servitore può avvenire in modo esplicito tramite le interfacce stabilite dal servitore in modo implicito tramite aree-dati accessibili ad entrambi.
FUNZIONI Una funzione permette di dare un nome a una espressione rendendola parametrica Esempi (pseudo-C): float f(){ * sin(0.75); } float f1(int x) { 2 + x * sin(0.75); }
FUNZIONI come COMPONENTI SW Una funzione è un componente software che cattura lidea mate- matica di funzione f : A B... Q S molti possibili ingressi (che non vengono modificati!) una sola uscita (il risultato)
FUNZIONI come SERVITORI Una funzione riceve dati di ingresso in corrispondenza ai parametri ha come corpo una espressione, la cui valutazione fornisce un risultato denota un valore in corrispondenza al suo nome
FUNZIONI come SERVITORI Esempio se x vale 1 e f è la funzione R R f = 3 * x 2 + x - 3 allora f(x) denota il valore 1.
FUNZIONI come SERVITORI Una funzione è un servitore passivo che serve un cliente per volta che può trasformarsi in cliente invocando se stessa
FUNZIONI come SERVITORI Una funzione è un servitore dotato di nome che incapsula le istruzioni che realizzano un certo servizio. Per chiedere al servitore di svolgere il servizio occorre –chiamare tale servitore (per nome) –fornirgli le necessarie informazioni Come far comunicare cliente e servitore?
COMUNICAZIONE CLIENTE/SERVITORE Nel caso di una funzione, cliente e servitore comunicano mediante linterfaccia della funzione.
FUNZIONI: INTERFACCIA Linterfaccia di una funzione comprende –nome della funzione –lista dei parametri –tipo del valore da essa denotato è spesso chiamata firma (signature) esplicita il contratto di servizio fra cliente e servitore.
COMUNICAZIONE CLIENTE/SERVITORE Cliente e servitore comunicano quindi mediante i parametri trasmessi dal cliente al servitore allatto della chiamata (direzione: dal cliente al servitore) il valore restituito dal servitore al cliente (direzione: dal servitore al cliente)
UN ESEMPIO int max (int x, int y ){ return x>y ? x : y; } Il simbolo max denota il nome della nuova funzione Le variabili intere x e y sono i parametri della nuova funzione Il valore restituito è un intero.
FUNZIONI & INFORMATION HIDING La struttura interna (corpo) di una funzione è completamente inaccessibile dallesterno. Così facendo si garantisce protezione dellinformazione secondo il principio del suo nascondimento (information hiding)
FUNZIONI IN UN PROGRAMMA C Un programma C è un insieme di funzioni: ::= { } ::= | | … Il main è semplicemente la funzione, di no- me prefissato, dove inizia lelaborazione.
DEFINIZIONE DI FUNZIONE: struttura Una è definita dalla produzione: ::= ( ){ }
DEFINIZIONE DI FUNZIONE: parametri Una è definita dalla produzione: ::= ( ){ } o una lista vuota: void o una lista di variabili (separate da virgole) visibili solo entro il corpo della funzione.
DEFINIZIONE DI FUNZIONE: corpo Una è definita dalla produzione: ::= ( ){ } è tutto ciò che sta fra {} La forma base è return ;
DEFINIZIONE DI FUNZIONE: tipo Una è definita dalla produzione: ::= ( ){ } il tipo della funzione deve coinci- dere col tipo dellespressione che costituisce il corpo della funzione
FUNZIONI COME COMPONENTI SOFTWARE: NASCITA E MORTE Allatto della chiamata, lesecuzione del cliente viene sospesa e il controllo passa al servitore. Il servitore vive solo per il tempo necessario a svolgere il servizio. Al termine, il servitore muore, e lesecuzione torna al cliente.
CHIAMATA DI FUNZIONE La chiamata di funzione è unespressione della forma ( ) dove: ::= [ ] {, }
Linvocazione di una funzione da parte di un cliente costituisce un particolare tipo di espressione. CHIAMATE DI FUNZIONE & ESPRESSIONI
RITORNO DA FUNZIONE Listruzione return provoca la restitu- zione del controllo al cliente, unitamente al valore dellespres- sione che la segue. Eventuali istruzioni successive alla return non saranno mai eseguite!
IL PROBLEMA DEI SIMBOLI f(x) + g( f(x), q( x + f(y))) Per fornire il risultato, dobbiamo conoscere: la definizione delle funzioni f, g, q i valori di x e y Come conoscerli?
SIMBOLI & SIGNIFICATO Come conoscere il significato di un simbolo? o esiste una convenzione diffusa, una cultura comune, che associa ad un simbolo un preciso significato; o esiste un ente di riferimento che specifica in modo esplicito il significato di un simbolo.
BINDING & ENVIRONMENT La conoscenza di cosa un simbolo denota viene espressa da una legame (binding) tra il simbolo e uno o più attributi. La collezione dei binding valida in (un certo punto di) un programma si chiama environment.
UN ESEMPIO Sia f N->N: f(x)=x+x Sia z = 2 f(z) + f(z) vale 8 f(2) + f(y) vale ??? g(z) vale ???
SCOPE & SCOPE RULES Tutte le occorrenze di un nome nel testo di un programma a cui si applica un dato binding si dicono essere entro la stessa portata o scope del binding. Le regole in base a cui si stabilisce la portata di un binding si dicono regole di visibilità o scope rules.
ESEMPIO Sia f N->N: f(x)=x+x Sia z = 2 f(z) + f(z) vale 8 f(2) + f(y) vale ??? Sia g N->N: g(x)=x*x g(z) vale 4 f(g(z))vale 8
DEFINIZIONE DI NUOVE FUNZIONI Per dare un nome alla nuova funzione, il linguaggio deve introdurre costrutti per estendere un environment aggiungendo ad esso il nuovo binding che lega il nome scelto per la funzione allentità che realizza quella specifica funzione.
DEFINIZIONE DI NUOVE FUNZIONI La definizione di funzione unisce la denotazione di una nuova funzione lattribuzione di un nome ad essa Lenvironment corrente viene arricchito con uno nuovo binding tra il nome della funzione, e la sua rappresentazione interna
FUNZIONI: IL MODELLO APPLICATIVO 1) Valutazione, nellenvironment corrente, del simbolo che denota il nome della funzione; 2) Valutazione, nellenvironment corrente, delle espressioni che denotano i parametri; 3) Commutazione allenvironment di definizione della funzione.
FUNZIONI: IL MODELLO APPLICATIVO 4)Chiamata della funzione; 5)Esecuzione del corpo della funzione (nel suo environment di definizione); 6)Restituzione al chiamante di –controllo –risultato con ripristino dellenvironment esistente al momento della chiamata.
LENVIRONMENT La definizione di una funzione introduce un nuovo binding nellenvironment di definizione della funzione –in C, il global environment Al momento dellinvocazione, si crea un nuovo environment –una struttura che contiene i binding dei parametri e degli identificatori dichiarati localmente alla funzione.
ESEMPIO int max (int x, int y ){ return x>y ? x : y; } Lenvironment corrente viene arricchito di un nuovo binding: max / CodiceDellaFunzione Quando max viene chiamata, si commuta a un nuovo environment, in cui sono definite le variabili intere x e y
COMUNICAZIONE CLIENTE SERVITORE Il cliente passa informazioni al servitore mediante una serie di parametri attuali. Parametri formali : –sono specificati nella dichiarazione del servitore –esplicitano il contratto fra servitore e cliente –indicano cosa il servitore si aspetta dal cliente Parametri attuali : –sono trasmessi dal cliente allatto della chiamata –devono corrispondere ai parametri formali in numero, posizione e tipo
COMUNICAZIONE CLIENTE SERVITORE Il cliente passa informazioni al servitore mediante una serie di parametri attuali. I Parametri attuali sono legati ai parametri formali al momento della chiamata, in modo dinamico. Tale legame: vale solo per linvocazione corrente vale solo per la durata della funzione.
IL NOSTRO ESEMPIO Il servitore... int max (int x, int y ){ return x>y ? x : y; } … e un possibile cliente: main(){ int z = 8; int m = max(2*z,13); }
IL NOSTRO ESEMPIO Il servitore... int max (int x, int y ){ return x>y ? x : y; } … e un possibile cliente: main(){ int z = 8; int m = max(2*z,13); } 1) Valutazione del simbolo z nellenvironment corrente Si trova che z vale 8.
IL NOSTRO ESEMPIO Il servitore... int max (int x, int y ){ return x>y ? x : y; } … e un possibile cliente: main(){ int z = 8; int m = max(2*z,13); } 2) Calcolo dellespressione 2*z nellenvironment corrente Si trova che vale 16.
IL NOSTRO ESEMPIO Il servitore... int max (int x, int y ){ return x>y ? x : y; } … e un possibile cliente: main(){ int z = 8; int m = max(2*z,13); } 3) Invocazione della funzione max con parametri attuali 16 e 13. Il controllo passa al servitore.
IL NOSTRO ESEMPIO Il servitore... int max (int x, int y ){ return x>y ? x : y; } … e un possibile cliente: main(){ int z = 8; int m = max(2*z,13); } 4) I parametri formali x e y vengono legati ai parametri attuali 16 e 13. Inizia lesecuzione del servitore.
IL NOSTRO ESEMPIO Il servitore... int max (int x, int y ){ return x>y ? x : y; } … e un possibile cliente: main(){ int z = 8; int m = max(2*z,13); } 5) Viene valutata lespressione condizionale nellenvironment del servitore. Il risultato è 16.
IL NOSTRO ESEMPIO Il servitore... int max (int x, int y ){ return x>y ? x : y; } … e un possibile cliente: main(){ int z = 8; int m = max(2*z,13); } 6) Il valore così determinato (16) viene restituito al cliente. Il servitore termina e il controllo torna al cliente.
IL NOSTRO ESEMPIO Il servitore... int max (int x, int y ){ return x>y ? x : y; } … e un possibile cliente: main(){ int z = 8; int m = max(2*z,13); } 7) Il valore restituito (16) viene usato per inizializzare la variabile m (nellenvironment del cliente)
RIASSUNTO Allatto dellinvocazione di una funzione: si crea una nuova attivazione (istanza) del servitore si alloca la memoria per i parametri (e le eventuali variabili locali) si trasferiscono i parametri al servitore si trasferisce il controllo al servitore si esegue il codice della funzione.
PASSAGGIO DEI PARAMETRI In generale, un parametro può essere trasferito: per valore o copia (by value) si trasferisce il valore del parametro attuale per riferimento (by reference) si trasferisce un riferimento al parametro attuale
PASSAGGIO PER VALORE si trasferisce una copia del valore del parametro attuale cliente z 45
PASSAGGIO PER VALORE si trasferisce una copia del valore del parametro attuale clienteservitore z 45 copia w 45 valore (copiato) di z istanza del servitore
PASSAGGIO PER VALORE si trasferisce una copia del valore del parametro attuale clienteservitore z 45 copia w 45 valore (copiato) di z istanza del servitore Ogni azione fatta su w è strettamente locale al servitore
PASSAGGIO PER RIFERIMENTO si trasferisce un riferimento al parametro attuale cliente z 45
PASSAGGIO PER RIFERIMENTO si trasferisce un riferimento al parametro attuale clienteservitore z 45 riferimento w riferimento a z (indirizzo) istanza del servitore
PASSAGGIO PER RIFERIMENTO si trasferisce un riferimento al parametro attuale clienteservitore z 45 riferimento w riferimento a z (indirizzo) istanza del servitore Ogni azione fatta su w è in realtà fatta sulla variabile z del cliente!
PASSAGGIO DEI PARAMETRI IN C In C, i parametri sono trasferiti sempre e solo per valore (by value) si trasferisce una copia del parametro attuale, non loriginale! tale copia è strettamente privata e locale a quel servitore il servitore potrebbe quindi alterare il valore ricevuto, senza che ciò abbia alcun impatto sul cliente
PASSAGGIO DEI PARAMETRI IN C In C, i parametri sono trasferiti sempre e solo per valore (by value) Conseguenza: è impossibile usare un parametro per trasferire informazioni verso il cliente per trasferire (una) informazione al cliente si sfrutta il valore di ritorno della funzione
ESEMPIO: VALORE ASSOLUTO Definizione formale: |x|: N N |x| vale x se x 0 |x| vale -x se x 0 Codifica sotto forma di funzione C: int valAss(int x) { return (x<0) ? -x : x; }
ESEMPIO: VALORE ASSOLUTO Servitore & Cliente: int valAss(int x) { return (x<0) ? -x : x; } main(){ int z = -87; int absz = valAss(z); int abs4 = valAss(4); }
ESEMPIO: VALORE ASSOLUTO Servitore & Cliente: int valAss(int x) { return (x<0) ? -x : x; } main(){ int z = -87; int absz = valAss(z); int abs4 = valAss(4); } Quando valAss(z) viene chiamata, il valore attuale di z, valutato nel- lenvironment corrente (-87), viene copiato e passato a valAss.
ESEMPIO: VALORE ASSOLUTO Servitore & Cliente: int valAss(int x) { return (x<0) ? -x : x; } main(){ int z = -87; int absz = valAss(z); int abs4 = valAss(4); } valAss riceve quindi una copia del valore -87 e la lega al simbolo x. Poi si valuta lespressione, che qui vale 87, e si restituisce questo valore.
ESEMPIO: VALORE ASSOLUTO Servitore & Cliente: int valAss(int x) { return (x<0) ? -x : x; } main(){ int z = -87; int absz = valAss(z); int abs4 = valAss(4); } Il valore restituito (87) viene usato per inizializzare la variabile absz.
ESEMPIO: MASSIMO DI DUE NUMERI Definizione formale: max: N N N max(x,y) vale x se x y max(x,y) vale y se x < y Codifica sotto forma di funzione C: int max(int x, int y) { return (x<y) ? y : x; }
ESEMPIO: MASSIMO DI DUE NUMERI Servitore & Cliente: int max(int x, int y) { return (x<y) ? y : x; } main(){ int z = -87, y = 12; int m = max(z,18); int n = max(4*y,z+100); }
ESEMPIO: MASSIMO DI DUE NUMERI Servitore & Cliente: int max(int x, int y) { return (x<y) ? y : x; } main(){ int z = -87, y = 12; int m = max(z,18); int n = max(4*y,z+100); } Si valutano le due espressioni che costituiscono i parametri attuali (nel- lenvironment del main) e si trasmette alla funzione copia dei valori ottenuti.
ESEMPIO: MASSIMO DI DUE NUMERI Servitore & Cliente: int max(int x, int y) { return (x<y) ? y : x; } main(){ int z = -87, y = 12; int m = max(z,18); int n = max(4*y,z+100); } La funzione riceve copia dei valori -87 e 18, e li lega ai simboli x e y. Poi si valuta lespressione. Il risultato (18) è restituito al main chiamante.
PASSAGGIO DEI PARAMETRI IN C Perché il C adotta sempre e solo il passaggio per valore (by value)? è sicuro: le variabili del chiamante e del chiamato sono completamente disac- coppiate consente di ragionare per componenti e servizi: la struttura interna dei singoli componenti è irrilevante
PASSAGGIO DEI PARAMETRI IN C Limiti: consente di restituire al cliente solo valori di tipo (relativamente) semplice non consente di restituire collezioni di valori non consente di scrivere componenti software il cui scopo sia diverso dal calcolo di una espressione
PASSAGGIO DEI PARAMETRI Molti linguaggi mettono a disposizione il passaggio per riferimento (by reference) non si trasferisce una copia del valore del parametro attuale si trasferisce un riferimento al parametro, in modo da dare al servitore accesso di- retto al parametro in possesso del cliente il servitore accede e modifica direttamente il dato del cliente.
PASSAGGIO DEI PARAMETRI IN C Il C non supporta direttamente il passaggio per riferimento è una grave mancanza! il C lo fornisce indirettamente solo per alcuni tipi di dati ergo, occorre costruirselo quando serve. (vedremo dei casi) Il C++ e Java invece lo forniscono.
DEFINIZIONE DI NUOVE FUNZIONI & STRATEGIE DI COMPOSIZIONE La capacità di definire nuove funzioni permette: di definire nuove operazioni di introdurre variabili per denotare i dati in modo simbolico di esprimere la ripetizione di una espressione per un numero (prefissato o meno) di volte.
STRATEGIE DI COMPOSIZIONE Tre grandi approcci: 1) la composizione di funzioni; 2) le espressioni condizionali; 3) la ricorsione. Le funzioni definibili in termini di un insieme prescelto di primitive e delle precedenti strategie di composizione costituiscono un insieme detto delle funzioni ricorsive generali.
1) COMPOSIZIONE DI FUNZIONI I parametri in una chiamata di funzione pos- sono consistere/comprendere altre funzioni Es: f(x) + g(f(x), q(x + f(y))) x1 = f(x) x2 = f(x) (mossa evitabile da un automa intelligente ) x3 = f(y) x4 = x + x3 x5 = q( x4 ) x6 = g( x2,x5 ) x7 = x1 + x6
2) ESPRESSIONE CONDIZIONALE Lespressione condizionale riflette la consuetudine matematica di definire le funzioni per elencazione di casi. Esempio: abs: N -> N abs(x) vale x se x 0 abs(x) vale -x se x 0
3) LA RICORSIONE La ricorsione consiste nella pos- sibilità di definire una funzione in termini di se stessa. È basata sul principio di induzione matematica: –se una proprietà P vale per n=n 0 –e si può provare che, assumendola valida per n, allora vale per n+1 allora P vale per ogni n n 0
LA RICORSIONE Operativamente, risolvere un problema con un approccio ricorsivo comporta –di identificare un caso base la cui soluzione sia ovvia –di riuscire a esprimere la soluzione al caso generico n in termini dello stesso problema in uno o più casi più semplici (n-1, n-2, etc).
LA RICORSIONE: ESEMPIO Esempio !: N N n! vale 1 se n 0 n! vale n*(n-1)! se n > 0 Codifica: int fact(int n) { return n==0 ? 1 : n*fact(n-1); }
LA RICORSIONE: ESEMPIO Esempio !: N N n! vale 1 se n 0 n! vale n*(n-1)! se n > 0 Codifica: int fact(int n) { return n==0 ? 1 : n*fact(n-1); } Attenzione: la codifica non corrisponde alla specifica!! Il 2° caso si applica per n 0, cioè anche per n<0 !! MA COSÌ PUÒ NON TERMINARE!
LA RICORSIONE: ESEMPIO Esempio !: N N n! vale 1 se n 0 n! vale n*(n-1)! se n > 0 Codifica: int fact(int n) { /* return n==0 ? 1 : n*fact(n-1); */ return n>0 ? n*fact(n-1) : 1; } Nuova codifica
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); }
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Si valuta lespressione che costituisce il parametro attuale (nellenvironment del main) e si trasmette alla funzione fatt una copia del valore così ottenuto (7).
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } La funzione riceve una copia del valore 7 e la lega al simbolo n. Poi valuta lespres- sione condizionale: ciò impone di valutare una espressione che contiene una nuova chiamata di funzione.
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Si valuta quindi, nellenvironment di fatt, lespressione n-1 (che vale 6), e si effettua una nuova chiamata al servitore fatt, pas- sandogli una copia del valore 6.
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Il (nuovo) servitore riceve quindi una copia del valore 6 e, come sopra, valuta lespres- sione condizionale. Ciò lo porta a dover fare una nuova chiamata passando 5.
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } … la cosa prosegue, servitore dopo servitore.....
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Prima o poi, dato che il valore passato cala ogni volta, si giunge a invocare fatt con parametro 1. In questo caso, la valutazione dellespressione dà come risultato 1.
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Ciò chiude la sequenza di chiamate ricorsive. Il controllo torna al servitore precedente, che può finalmente valutare lespressione n*1 (valutando n nel suo environment, dove vale 2) ottenendo 2.
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Il valore 2 viene restituito al servitore pre- cedente, che a sua volta può così valutare lespressione n*2 (valutando n nel suo environment, dove vale 3) ottenendo 6.
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } … la cosa prosegue...
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Prima o poi, a forza di retrocedere, si torna al primo servitore attivato, che può quindi valutare lespressione n*720 (valutando n nel suo environment, dove vale 7), giun- gendo così a trovare il valore 5040.
ESEMPIO: FATTORIALE Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Il valore 5040, restituito dal servitore fatt, può quindi essere usato per inizializzare la variabile fz.
UN ALTRO ESEMPIO Problema: calcolare la somma dei primi N interi Specifica: Considera la somma ( (N-1)+N come composta di due termini: ( (N-1)) N Esiste un caso banale assolutamente ovvio: la somma fino a 1 vale 1.
UN ALTRO ESEMPIO Problema: calcolare la somma dei primi N interi Specifica: Considera la somma ( (N-1)+N come composta di due termini: ( (N-1)) N Esiste un caso banale assolutamente ovvio: la somma fino a 1 vale 1. Il primo termine non è altro che la soluzione allo stesso problema in un caso più semplice Il secondo termine è un valore già noto.
UN ALTRO ESEMPIO Problema: calcolare la somma dei primi N interi Codifica: int sommaFinoA(int n){ return (n==1) ? 1 : sommaFinoA(n-1) + n; }
UN TERZO ESEMPIO Problema: calcolare lN-esimo numero di Fibonacci 0, se n=0 fib(n-1) + fib(n-2), altrimenti fib (n) =1, se n=1
UN TERZO ESEMPIO Problema: calcolare lN-esimo numero di Fibonacci Codifica: unsigned fibonacci(unsigned n) { return (n<2) : n ? fibonacci(n-1) + fibonacci(n-2); }
UN TERZO ESEMPIO Problema: calcolare lN-esimo numero di Fibonacci Codifica: unsigned fibonacci(unsigned n) { return (n<2) : n ? fibonacci(n-1) + fibonacci(n-2); } Ricorsione non lineare: ogni invocazione del servitore causa due nuove chiamate al servitore medesimo.
UNA RIFLESSIONE Negli esempi di ricorsione visti finora fattoriale somma dei primi N interi calcolo dellN-esimo numero di Fibonacci si inizia a sintetizzare il risultato solo dopo che si sono aperte tutte le chiamate, a ritroso, mentre le chiamate si chiudono.
UNA RIFLESSIONE Le chiamate ricorsive decompongono via via il problema, ma non calcolano nulla Il risultato viene sintetizzato a partire dalla fine, perché prima occorre arrivare al caso banale: il caso banale fornisce il valore di partenza poi, e solo poi, si sintetizzano, a ritroso, i successivi risultati parziali.
UNA RIFLESSIONE Ciò indica che tali soluzioni sono sintatticamente ricorsive e danno luogo a un processo computa- zionale effettivamente ricorsivo.
UN ESEMPIO DIVERSO Problema: trovare il Massimo Comun Divisore tra N e M m, se m=n MCD(m, n-m),se m<n MCD(m, n) =MCD(m-n, n),se m>n
UN ESEMPIO DIVERSO Problema: calcolare il Massimo Comun Divisore tra N e M Codifica: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); }
UN ESEMPIO DIVERSO Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } main(){ int m = mcd(36,15); }
UN ESEMPIO DIVERSO Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } main(){ int m = mcd(36,15); } Si valutano i parametri attuali e si invoca la funzione con parametri 36 e 15
UN ESEMPIO DIVERSO Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } main(){ int m = mcd(36,15); } Si legano i parametri 36 e 15 ai parametri formali m e n, e si valuta lespressione condizionale.
UN ESEMPIO DIVERSO Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } main(){ int m = mcd(36,15); } Poiché 36 15, si invoca nuovamente la funzione con parametri m-n (21) e n (15).
UN ESEMPIO DIVERSO Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } main(){ int m = mcd(36,15); } Il nuovo servitore, poiché 21 15, fa la stessa cosa e invoca nuovamente la funzione con parametri m-n (6) e n (15).
UN ESEMPIO DIVERSO Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } main(){ int m = mcd(36,15); } Il nuovo servitore, poiché 6 15, invoca un ulteriore servitore con parametri m (6) e n-m (9).
UN ESEMPIO DIVERSO Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } main(){ int m = mcd(36,15); } Poiché 6 9, si ha una nuova chiamata con parametri m (6) e n-m (3).
UN ESEMPIO DIVERSO Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } main(){ int m = mcd(36,15); } Poiché 6 3, si ha una nuova invocazione (lultima!) con parametri m-n (3) e n (3).
UN ESEMPIO DIVERSO Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } main(){ int m = mcd(36,15); } Poiché 3 = 3, il servitore termina e restituisce come risultato 3.
UN ESEMPIO DIVERSO Perché questo esempio è diverso ? il risultato viene sintetizzato via via che le chiamate si aprono, in avanti quando le chiamate si chiudono non si fa altro che riportare indietro, fino al cliente, il risultato ottenuto.
UNA RIFLESSIONE La soluzione ricorsiva individuata per lMCD è sintatticamente ricorsiva... … ma dà luogo a un processo computa- zionale diverso dal precedente: un processo computazionale ITERATIVO Il risultato viene sintetizzato in avanti ogni passo decompone e calcola e porta in avanti il nuovo risultato parziale
UNA RIFLESSIONE Ogni processo computazionale ITERATIVO calcola a ogni passo un risultato parziale dopo k passi, si ha a disposizione il risultato parziale relativo al caso k questo non è vero nei processi computa- zionali ricorsivi là, finché non si sono aperte tutte le chiamate, non è disponibile nessun risultato!
RICORSIONE TAIL Una ricorsione che realizza un processo computazionale ITERATIVO è una ricorsione solo apparente la chiamata ricorsiva è sempre lultima istruzione i calcoli sono fatti prima la chiamata serve solo, dopo averli fatti, per proseguire la computazione questa forma di ricorsione si chiama RICORSIONE TAIL (ricorsione in coda)