Äîêóìåíò âçÿò èç êýøà ïîèñêîâîé ìàøèíû. Àäðåñ
îðèãèíàëüíîãî äîêóìåíòà
: http://www.arcetri.astro.it/irlab/doc/library/linux/AppLinux/al189.htm
Äàòà èçìåíåíèÿ: Tue Sep 21 18:08:51 1999 Äàòà èíäåêñèðîâàíèÿ: Sat Dec 22 13:24:29 2007 Êîäèðîâêà: Ïîèñêîâûå ñëîâà: polar ring |
Nel capitolo introduttivo, sono state elencate le strutture elementari per il controllo e il raggruppamento delle istruzioni (espressioni) di Scheme. In questo capitolo, si vuole mostrare in che modo possano essere definite delle funzioni, o comunque dei raggruppamenti di istruzioni all'interno dei quali si possa individuare un campo di azione locale per le variabili che vi vengono dichiarate al suo interno.
Le funzioni del linguaggio Scheme prevedono il passaggio di parametri solo per valore, e questo significa che gli argomenti di una funzione vengono valutati prima di tutto. Al posto del passaggio dei parametri per riferimento, Scheme consente l'indicazione di espressioni costanti, concetto a cui si Õ accennato nel capitolo precedente.
La definizione e inizializzazione di un oggetto avviene normalmente attraverso la funzione define
. Questa puÐ servire per dichiarare una variabile normale, o anche per dichiarare una funzione.
(define <nome-variabile> <espressione-di-inizializzazione>) |
Quello che si vede sopra Õ appunto lo schema sintattico per la dichiarazione e inizializzazione di una variabile, cosa che Õ stata vista piÛ volte nel capitolo precedentemente. Sotto, si vede lo schema sintattico per la dichiarazione di una funzione:
(define (<nome-funzione> <elenco-parametri-formali>) <corpo> ) |
In questo caso, i parametri formali sono una serie di nomi che rappresentano i parametri della funzione che viene dichiarata, e il corpo sono una serie di espressioni, che rappresentano il contenuto della funzione che si dichiara. Il valore che viene restituito dall'ultima espressione che viene eseguita all'interno della funzione, Õ ciÐ che restituisce la funzione stessa. L'esempio seguente, serve a definire la funzione moltiplica
con due parametri, x
e y
, che restituisce il prodotto dei suoi due argomenti:
(define (moltiplica x y) ; il corpo di questa funzione Õ molto breve (* x y) ) |
Per chiamare questa funzione, basta semplicemente un'istruzione come quella seguente:
(moltiplica 10 11) ===> 110 |
Le dichiarazioni di questo tipo, cioÕ di variabili e di funzioni, possono avvenire solo nella parte piÛ esterna di un programma Scheme, oppure all'interno della dichiarazione di altre funzioni e delle altre strutture descritte in questo capitolo, ma in tal caso devono apparire all'inizio del «corpo» delle espressioni che queste strutture contengono. Si osservi l'esempio seguente, in cui viene dichiarata una funzione e al suo interno si dichiarano altre variabili locali:
(define (moltiplica x y) ; dichiara le variabili locali (define z 0) ; definisce un ciclo enumerativo, per il calcolo del prodotto ; attraverso la somma, in cui viene dichiarata implicitamente ; la variabile «i». (do ((i 1 (+ i 1))) ; condizione di uscita ((> i y)) ; istruzioni del ciclo (set! z (+ z x)) ) ; al termine restituisce il valore contenuto nella variabile «z» z ) |
Dovrebbe essere intuitivo, quindi, che il campo di azione delle variabili dichiarate all'interno di una funzione define
Õ limitato alla funzione stessa. La stessa cosa varrebbe per le funzioni, dichiarate all'interno di un ambiente del genere. Si osservi l'esempio seguente, in cui si calcola il prodotto tra due numeri, a partire dalla somma di questi, ma dove la somma si ottiene da un'altra funzione, locale, in cui questa si ottiene sommando un'unitÞ alla volta.
(define (moltiplica x y) ; dichiara la funzione «somma», locale nell'ambito della ; funzione «moltiplica» (define (somma x y) ; dichiara una variabile locale per la funzione «somma», ; che comunque non serve a nulla :-) (define z 2000) ; definisce un ciclo enumerativo, per il calcolo della ; somma, sommando un'unitÞ alla volta (do () ; condizione di uscita ((<= y 0)) ; istruzioni del ciclo (set! x (+ x 1)) ; decrementa «y» (set! y (- y 1)) ) ; al termine restituisce il valore contenuto nella variabile «x» x ; fine della funzione locale «somma» ) ; dichiara le variabili locali della funzione «moltiplica» (define z 0) ; definisce un ciclo enumerativo, per il calcolo del prodotto ; attraverso la somma, in cui viene dichiarata implicitamente ; la variabile «i». (do ((i 1 (+ i 1))) ; condizione di uscita ((> i y)) ; istruzioni del ciclo (set! z (somma z x)) ) ; al termine restituisce il valore contenuto nella variabile «z» z ) |
Questo esempio Õ solo un pretesto per mostrare che le variabili locali x
, y
e z
, della funzione somma
hanno effetto solo nell'ambito di questa funzione, mentre la funzione somma
, e le variabili locali x
, y
e z
della funzione moltiplica
hanno effetto solo nell'ambito della funzione moltiplica
stessa.
Nel capitolo introduttivo si Õ accennato al fatto che la ridefinizione di una variabile, o di una funzione, implica una nuova allocazione di memoria, senza liberare quella utilizzata precedentemente. Questo implica che i riferimenti fatti in precedenza a quell'oggetto, continuano a utilizzare in pratica la vecchia allocazione. Si osservi l'esempio seguente:
(define x 20) x ===> 20 (define y (* 2 x)) y ===> 40 (define x 100) x ===> 100 y ===> 40 |
Quando mostrato con questo esempio, non ha nulla di eccezionale, rispetto ai linguaggi di programmazione tradizionali. Tuttavia, potrebbe risultare strano da un punto di vista prettamente matematico. Se invece lo scopo fosse quello di definire un sistema di equazioni, y
dovrebbe essere trasformato in una funzione, come nell'esempio seguente:
(define x 20) x ===> 20 (define (f y) (* 2 x)) (f) ===> 40 (define x 100) x ===> 100 (f) ===> 200 |
Qualunque oggetto con un identificatore puÐ essere ridefinito. Si osservi l'esempio seguente, in cui si imbrogliano le carte e si fa in modo che l'identificatore *
corrisponda a una funzione che esegue la somma, e non piÛ la moltiplicazione:
(define (* x y) (+ x y) (* 3 5) ===> 8 |
Si ricorda che per modificare il contenuto di una variabile allocata, senza allocare un'altra area di memoria, si utilizza generalmente la funzione set!
.
Scheme tratta gli identificatori delle funzioni (i loro nomi), nello stesso modo di quelli delle variabili. In altri termini, le funzioni sono variabili che contengono un riferimento a un blocco di codice. õ possibile dichiarare una funzione attraverso la funzione lambda
, che restituisce la funzione stessa. In questo modo, una funzione puÐ essere dichiarata anche attraverso l'assegnamento di una variabile, che poi diventa una funzione a tutti gli effetti.
Prima di vedere come si usa la dichiarazione di una funzione attraverso la funzione lambda
, Õ bene ribadire questo concetto: attraverso questo meccanismo, Õ possibile dichiarare una funzione in tutte quelle situazioni in cui Õ possibile inizializzare o assegnare una variabile.
(lambda (<elenco-parametri-formali>) <corpo> ) |
Come si vede dal modello sintattico, la funzione lambda
Õ relativamente semplice: il primo argomento Õ un blocco contenente l'elenco dei nomi (locali) dei parametri formali; gli argomenti successivi sono le espressioni che costituiscono il corpo della funzione. Non si dichiara il nome della funzione, dal momento che lambda
restituisce la funzione stessa, che verrÞ identificata (ammesso che lo si voglia fare) dalla variabile a cui questa viene assegnata.
All'inizio del «corpo» delle espressioni che descrivono il contenuto della funzione che si dichiara, si possono inserire delle dichiarazioni ulteriori attraverso la funzione define
.
Sotto vengono proposti alcuni esempi che dovrebbero lasciare intendere in quante situazioni si puÐ utilizzare una dichiarazione di funzione attraverso lambda
.
; dichiara la variabile «f», e la inizializza temporaneamente al valore zero (define f 0) ; assegna a «f» una funzione che esegue la somma dei suoi due argomenti (set! f (lambda (x y) (+ x y) ) ) ; calcola la somma tra 4 e 5, restituendo 9 (f 4 5) |
L'esempio che appare sopra mostra in che modo si possa dichiarare una funzione in qualunque situazione in cui si puÐ assegnare un valore a una variabile.
; dichiara direttamente la funzione «f» (define f ; inizializza «f» con una funzione che esegue la somma ; dei suoi due argomenti (lambda (x y) ; corpo della dichiarazione della funzione (+ x y) ) ) ; calcola la somma tra 4 e 5, restituendo 9 (f 4 5) |
In questo caso, l'assegnamento della funzione alla variabile f
Õ avvenuto contestualmente alla dichiarazione della variabile stessa.
(define moltiplica (lambda (x y) ; dichiara le variabili locali (define z 0) ; definisce un ciclo enumerativo, per il calcolo del prodotto ; attraverso la somma, in cui viene dichiarata implicitamente ; la variabile «i». (do ((i 1 (+ i 1))) ; condizione di uscita ((> i y)) ; istruzioni del ciclo (set! z (+ z x)) ) ; al termine restituisce il valore contenuto nella variabile «z» z ) ) |
Questo esempio, mostra in che modo possano avvenire delle dichiarazioni locali nel corpo di una dichiarazione lambda
.
L'esempio successivo Õ un po' un estremo, nel senso che viene mostrata la dichiarazione di una funzione «anonima», che viene usata immediatamente per calcolare il prodotto tra 3 e 4. Successivamente al suo utilizzo istantaneo, non c'Õ modo di riutilizzare tale funzione.
( ; dichiarazione della funzione anonima (lambda (x y) ; dichiara le variabili locali (define z 0) ; definisce un ciclo enumerativo, per il calcolo del prodotto ; attraverso la somma, in cui viene dichiarata implicitamente ; la variabile «i». (do ((i 1 (+ i 1))) ; condizione di uscita ((> i y)) ; istruzioni del ciclo (set! z (+ z x)) ) ; al termine restituisce il valore contenuto nella variabile «z» z ) ; indicazione del primo argomento 3 ; indicazione del secondo argomento 4 ) |
Si intuisce la possibilitÞ di Scheme di scrivere funzioni ricorsive. Non dovrebbe essere difficile arrivare a questo risultato senza spiegazioni particolari. L'esempio seguente mostra il calcolo del fattoriale attraverso una funzione ricorsiva.
(define (fattoriale n) (if (= n 0) ; then 1 ; else (* n (fattoriale (- n 1))) ) ) |
Si intuisce che una funzione senza nome, come nel caso di quella dichiarata con lambda
, senza assegnarla a una variabile, non puÐ essere resa ricorsiva, a meno di definire una sotto-funzione ricorsiva al suo interno. L'esempio seguente Õ una variante di quello precedente, in cui viene utilizzata una dichiarazione lambda
.
(define fattoriale (lambda (n) (if (= n 0) ; then 1 ; else (* n (fattoriale (- n 1))) ) ) ) |
Le funzioni let
, let*
e letrec
, hanno lo scopo di circoscrivere un ambiente, all'interno del quale puÐ essere inserita una serie indefinita di espressioni (istruzioni), prima delle quali vengono dichiarate delle variabili il cui campo di azione Õ locale rispetto a quell'ambito.
(let ((<variabile> <inizializzazione>)...) <corpo> ) |
(let* ((<variabile> <inizializzazione>)...) <corpo> ) |
(letrec ((<variabile> <inizializzazione>)...) <corpo> ) |
In tutti e tre le forme, le variabili vengono inizializzate e quindi si passa alla valutazione delle espressioni successive (le istruzioni). Alla fine, la funzione restituisce il valore dell'ultima espressione a essere stata eseguita al suo interno.
Nel caso di let
, le variabili vengono dichiarate e inizializzate senza un ordine preciso, ma semplicemente prima di passare alla valutazione delle espressioni successive:
(let ((x 1) (y 2)) (+ x y) ) ===> 3 |
L'esempio non ha un grande significato da un punto di vista pratico, ma si limita a mostrare intuitivamente come si comporta la funzione let
. In questo caso, vengono dichiarate le variabili locali x
e y
, inizializzandole rispettivamente a 1 e 2, infine viene calcolata semplicemente la somma tra le due variabili, cosa che restituisce il valore 3.
Nel caso di let*
, le variabili vengono dichiarate e inizializzate nell'ordine in cui sono (da sinistra a destra), e per questo, ogni inizializzazione puÐ fare riferimento alle variabili dichiarate precedentemente nella stessa sequenza:
(let* ((x 1) (y (+ x 1))) (+ x y) ) ===> 3 |
L'esempio mostra che la variabile locale y
viene inizializzata partendo dal valore della variabile locale x
, incrementando il valore di questa di un'unitÞ.
La funzione letrec
Õ piÛ sofisticata; il nome sta per let recursive. õ un po' difficile spiegare il senso di questa; si tenta almeno di mostrare la cosa in modo intuitivo.
Nello stesso modo in cui si puÐ dichiarare una variabile, si puÐ dichiarare una funzione. In questo senso, tali dichiarazioni possono anche essere ricorsive all'interno di una funzione letrec
. Viene mostrato un esempio tratto da R5RS:
(letrec ; dichiara le «variabili», che in realtÞ sono funzioni (predicati) ( ; dichiara la funzione «pari?» (pari? (lambda (n) (if (zero? n) ; il numero Õ pari #t ; altrimenti si prova a vedere se Õ dispari (dispari? (- n 1)) ) ) ) ; dichiara la funzione «dispari?» (dispari? (lambda (n) (if (zero? n) ; il numero Õ dispari #f ; altrimenti si prova a vedere se Õ pari (pari? (- n 1)) ) ) ) ) ; fine della dichiarazione delle variabili ; verifica che il numero 88 Õ pari, chiamando la funzione ; «pari?» dichiarata all'inizio (pari? 88) ; la chiamata restituisce il valore #t, e di conseguenza ; Õ questo il valore restituiti da tutto ) |
Le variabili pari?
e dispari?
vengono inizializzate assegnando loro una funzione dichiarata con lambda
, e il loro scopo Õ quello di verificare che l'argomento sia rispettivamente un numero pari o dispari.
(pari? 2) ===> #t (dispari? 2) ===> #f |
Tali variabili, e di conseguenza queste funzioni, hanno effetto solo nell'ambito della dichiarazione letrec
, al termine della quale diventano semplicemente irraggiungibili. Il principio di funzionamento di queste funzioni, sta nel fatto che lo zero sia pari, di conseguenza:
(pari? 0) ===> #t (dispari? 0) ===> #f |
Per tutti i numeri superiori, invece, Õ sufficiente verificare in modo ricorsivo di che tipo Õ il valore n-
1. Per la precisione, se si sta verificando il fatto che un numero sia pari, se questo Õ superiore a zero, si puÐ verificare che quel numero, meno uno, sia dispari, e cosË di seguito.
Queste tre strutture sono importanti soprattutto perchÈ consentono di inserire nel corpo delle dichiarazioni di variabili o di funzioni, e perchÈ cosË circoscrivono un ambito locale per queste. Come si Õ visto, queste dichiarazioni possono essere fatte anche prima (anche con let
e let*
), tenendo conto dell'ordine di valutazione che ognuna di queste strutture garantisce.
(let ((x 1) (y 2)) (define messaggio "sto calcolando la somma...") (display messaggio) (newline) (+ x y) ) ===> 3 |
L'esempio che si vede sopra, Õ solo un'estensione di quanto giÞ visto sopra, allo scopo di mostrare la possibilitÞ di utilizzare la funzione define
all'inizio del corpo di espressioni che contiene. L'esempio successivo Õ una «deviazione» ulteriore, in cui il messaggio viene dichiarato tra le variabili iniziali di let
.
(let ((x 1) (y 2) (messaggio "sto calcolando la somma...")) (display messaggio) (newline) (+ x y) ) ===> 3 |
---------------------------
Appunti Linux 1999.09.21 --- Copyright © 1997-1999 Daniele Giacomini -- daniele @ pluto.linux.it