Variabili Globali Le variabili globali sono variabili che possono venire usate da tutte le funzioni intendiamo definire e dal programma principale stesso. Si tratta di variabili che sono definite al di fuori del corpo di ogni funzione, e che sono accessibili da tutti i “blocchi” del programma. Per dichiarare una variabile globale, si mette la dichiarazione prima della definizione di ogni funzione. Si osservi che i nostri programmi possono contenere una sola variabile globale con un dato nome, diciamo n, mentre possono contenere molteplici variabili locali con lo stesso nome (n), dato che ciascuna di queste risiede in un differente contesto lessicale. Come vedremo, la funzione SETQ consente di creare variabili globali. 1 La funzione predefinita: SETQ In LisP è possibile stabilire il valore di un “atomo” mediante l’impiego di una funzione speciale, la funzione SETQ. Il suo effetto è quello di trasformare il valore del suo primo argomento nel valore del suo secondo argomento. Vediamo alcuni esempi, CG-USER(1): CG-USER(2): (SETQ ZERO 0) (0) (SETQ classifica ‘(milan juve lazio)) (MILAN JUVE INTER) La funzione predefinita SETQ consente l’assegnazione simultanea di più variabili, ad esempio, CG-USER(3): (SETQ pigreco 3.14 radicedidue 1.41 a pigreco) (3.14) ... naturalmente anche gli altri valori sono stati assegnati. In quest’ultimo caso, la funzione SETQ accetta n coppie di operandi e assegna ad ogni primo elemento di una coppia il valore del secondo elemento corrispondente. 2 Come usiamo SETQ Uno degli impieghi che possiamo fare della funzione SETQ è quello di creare, all’inizio del nostro programma, alcune variabili globali, i cui valori possano servirci, ad esempio, come valori di input per il programma stesso. Ad esempio, (SETQ (SETQ (SETQ (SETQ (SETQ string1 string2 string3 string4 string5 '(a b c d e 8 e d c b a)) '(Madam Im Adam)) '(A man a plan a canal Panama)) '(rats live on no evil star)) '(Sit on a potato pan otis)) (defun PPal(inlist) (cond ((null inlist) nil) . . . CG-USER(1): CG-USER(2): (Ppal string3) . . . (car string4) RATS 3 Variabili Locali Ogni variabile ha uno scope, che possiamo identificare con la porzione di codice nel quale è contenuta la variabile. Le variabili che compaiono fra gli argomenti di una funzione, dato che il loro scope è ristretto al corpo della funzione, sono dette variabili locali. Si consideri l’esempio seguente: (defun DOUBLE (n) (* n 2 )) Si osservi che, ad ogni chiamata della funzione DOUBLE, viene creata una nouva variabile locale di nome n. Dentro il corpo della funzione DOUBLE, il nome n si riferisce a questa variabile, ma, fuori dal corpo della funzione, può accadere che n sia il nome di un’altra variabile. 4 La funzione predefinita: LET La funzione predefinita LET ha come obiettivo la creazione di variabili locali. La sintassi generale della funzione è la seguente, (LET ( (var-1 value-1) (var-2 value-2) . . . (var-n value-n) ) body) Il corpo di LET segue esattamente le stesse regole del corpo di una qualunque funzione. 5 Un esempio per LET Quello che segue è un semplice esempio dell’uso della funzione LET per la creazione di variabili locali. Si osservi che la variabile SUM è “locale”, poichè la porzione di codice nella quale è contenuta (il suo scope), non è altro che il corpo della funzione MEDIA... fuori da questa funzione, SUM potrebbe non avere il significato che abbiamo inteso assegnarle. CG-USER(1): (defun MEDIA (x y) (let ((sum (+ x y))) (list x y 'media= (/ sum 2.0)))) CG-USER(2): (media 1 10) (1 10 MEDIA= 5.5) 6 ... con più di una variabile locale L’esempio seguente mostra come sia possibile creare simultaneamente più d’una variabile locale, CG-USER(1): (defun duetto (x) (let ((star (first x)) (co-star (third x))) (list co-star 'accompagnata 'da star))) CG-USER(2): (duetto '(fred e ginger)) (GINGER ACCOMPAGNATA DA FRED) 7 La funzione predefinita: LET* La funzione predefinita LET* è simile alla funzione LET ed ha il medesimo obiettivo: la creazione di variabili locali. L’unica differenza fra le due funzioni risiede nel fatto che LET* crea variabili locali in sequenza, consentendo l’utilizzazione delle variabili appena create. Ad esempio, CG-USER(1): (defun cambia-prezzo (old new) (let* ((diff (- new old)) (proporzione (/ diff old)) (percentuale (* proporzione 100.0))) (list ‘il ‘prezzo ‘è ‘variato ‘del percentuale ‘%))) CG-USER(2): (cambia-prezzo 5 15) (IL PREZZO E’ VARIATO DEL 200.0 %) Questo metodo per creare varibili locali è utile ogni volta che si intende assegnare un nome a diversi passi intermedi di una lunga computazione. 8 (I) Differenze fra LET e LET*... L’esempio seguente mostra un uso scorretto della funzione LET per la creazione di variabili locali. Sapreste indicare il perchè l’esempio provoca un malfunzionamento del programma? CG-USER(1): (defun faulty-size-range (x y z) (let ((biggest (max x y z)) (smallest (min x y z)) (r (/ biggest smallest 1.0)) ) (list 'factor 'of r) )) CG-USER(2): (faulty-size-range 34 35 20) Error: Attempt to take the value of the unbound variable `BIGGEST' 9 Il tracciato della valutazione di faulty-size-range (faulty-size-range 35 87 4) Enter FAULTY-SIZE-RANGE with inputs 35, 87, and 4 create variables X, Y, and Z, with values 35, 87, and 4 (let ...) (max x y z) 87 (min x y z) 4 (/ biggest smallest 1.0) Error! BIGGEST unassigned variable 10 (II) Differenze fra LET e LET*... L’esempio seguente mostra come assegnare correttamente i valori a più variabili, sfruttando la capacità della funzione LET* di creare le varibili in sequenza. CG-USER(3): (defun correct-size-range (x y z) (let* ((biggest (max x y z)) (smallest (min x y z)) (r (/ biggest smallest 1.0)) ) (list 'factor 'of r) )) CG-USER(4): (correct-size-range 34 35 20) (FACTOR OF 1.75) 11 Il tracciato della valutazione di correct-size-range (correct-size-range 35 87 4) Enter CORRECT-SIZE-RANGE with inputs 35, 87, and 4 create variables X, Y, and Z, with values 35, 87, and 4 (let* ...) (max x y z) 87 create variable BIGGEST, with value 87 (min x y z) 4 create variable SMALLEST, with value 4 (/ biggest smallest 1.0) 21.75 create variable R, with value 21.75 (list ’factor ’of r) (FACTOR OF 21.75) Result of LET* is (FACTOR OF 21.75) Result of CORRECT-SIZE-RANGE is (FACTOR OF 21.75) 12 Il Crivello di Eratostene Algoritmo che consente di individuare tutti i numeri primi minori di un dato numero Supponiamo di voler trovare tutti i numeri primi minori o uguali ad n. Assai poco formalmente, diciamo che l’algoritmo procede manipolando la lista dei numeri che vanno da 3 a n (il caso in cui n è uguale a 1 ed n uguale a 2,rappresentano i casi di terminazione dell’algoritmo). Ricorsivamente, il problema viene decomposto in problemi più semplici: per liste di lunghezza n-1, n-2, n-3, ..., fino alla “base” della definizione. La ricostruzione della lista, dunque, avviene testando se ogni nuovo elemento che deve essere aggiunto è o meno multiplo di uno almeno dei numeri già presenti, nel qual caso viene omesso dalla ricostruzione e si passa a testare il numero successivo. Il controllo su n rappresenta l’ultimo passo della computazione. La lista che in tal modo si ottiene non contiene alcun numero che risulti multiplo di altri numeri (nessun numero precedente lo divide)... ogni elemento della lista è un numero primo ! 13 CG-USER(1): (funf 7) 0[4]: (FUNF 7) 1[4]: (FUNF 6) 2[4]: (FUNF 5) 3[4]: (FUNF 4) 4[4]: (FUNF 3) 5[4]: (FUNF 2) 5[4]: returned (2) 5[4]: (FUNG 3 (2)) 6[4]: (FUNH 3 (2)) 7[4]: (FUNK 2 3) 7[4]: returned NIL 7[4]: (FUNH 3 NIL) 7[4]: returned NIL 6[4]: returned NIL 5[4]: returned (3 2) 4[4]: returned (3 2) 4[4]: (FUNG 4 (3 2)) 5[4]: (FUNH 4 (3 2)) 6[4]: (FUNK 3 4) 6[4]: returned NIL 6[4]: (FUNH 4 (2)) 7[4]: (FUNK 2 4) 7[4]: returned T 6[4]: returned T 5[4]: returned T 4[4]: returned (3 2) 3[4]: returned (3 2) 3[4]: (FUNG 5 (3 2)) 4[4]: (FUNH 5 (3 2)) 5[4]: (FUNK 3 5) 5[4]: returned NIL 5[4]: (FUNH 5 (2)) 6[4]: (FUNK 2 5) 6[4]: returned NIL ... ... ... ... ... ... 6[4]: (FUNH 5 NIL) 6[4]: returned NIL 5[4]: returned NIL 4[4]: returned NIL 3[4]: returned (5 3 2) 2[4]: returned (5 3 2) 2[4]: (FUNG 6 (5 3 2)) 3[4]: (FUNH 6 (5 3 2)) 4[4]: (FUNK 5 6) 4[4]: returned NIL 4[4]: (FUNH 6 (3 2)) 5[4]: (FUNK 3 6) 5[4]: returned T 4[4]: returned T 3[4]: returned T 2[4]: returned (5 3 2) 1[4]: returned (5 3 2) 1[4]: (FUNG 7 (5 3 2)) 2[4]: (FUNH 7 (5 3 2)) 3[4]: (FUNK 5 7) 3[4]: returned NIL 3[4]: (FUNH 7 (3 2)) 4[4]: (FUNK 3 7) 4[4]: returned NIL 4[4]: (FUNH 7 (2)) 5[4]: (FUNK 2 7) 5[4]: returned NIL 5[4]: (FUNH 7 NIL) 5[4]: returned NIL 4[4]: returned NIL 3[4]: returned NIL 2[4]: returned NIL 1[4]: returned (7 5 3 2) 0[4]: returned (7 5 3 2) (7 5 3 2) 14 L’algoritmo di Eratostene (defun FUNF (n) (cond ((<= n 1) nil) ((= n 2) '(2)) (t (FUNG n (FUNF (1- n)))) )) La funzione FUNF è la funzione principale; definisce la “base” (cioè, le condizioni di terminazione per la ricorsione) ed il “passo” ricorsivo. “1- n” e “1+ n” sono funzioni predefinite di incremento e decremento di uno. (defun FUNG (n ps) (cond ((FUNH n ps) ps) (t (cons n ps)) )) Se il controllo eseguito dalla funzione FUNH è positivo - cioè se n è multiplo di qualche numero già presente nella lista la funzione FUNG restituisce la lista senza n; restituisce una nuova lista con in testa n, altrimenti. (defun FUNH (n p) (cond ((null p) nil) ((numberp p) (FUNK p n)) ((FUNK (car p) n) t) (t (FUNH n (cdr p))) )) La funzione FUNH, a valori nell’insieme dei booleani {T, NIL}: ritorna il vero, se n è multiplo di qualche numero già presente nella lista, il falso, altrimenti. Il controllo che svolge riguarda il numero n la testa della lista di numeri che, ricorsivamente, è stata decomposta e “in parte” ricostruita. FUNH esegue il controllo su tutti glli elementi della lista (ricorsione sulla coda). (defun FUNK (n m) (cond ((integerp (/ m n)) t) (t nil) )) La funzione FUNK, a valori nell’insieme dei booleani {T, NIL}: ritona il vero, se il suo secondo argomento è multiplo del primo; il falso, altrimenti. Si osservi l’uso di integerp: non vale il metodo tradizionale per testare se 15 n divide m.