Il linguaggio Scheme ha una filosofia che si basa fondamentalmente sul suo tipo di notazione. Scheme è un linguaggio utile per rappresentare un problema, più che per realizzare un programma completo. La standardizzazione di questo linguaggio è riferita fondamentalmente a un documento che viene aggiornato periodicamente: RnRS, ovvero Revised-n Report on the Algorithmic Language Scheme, dove n è il numero di questa revisione (attualmente dovrebbe trattarsi della quinta). Tuttavia, la standardizzazione riguarda gli aspetti fondamentali del linguaggio, mentre ogni realizzazione che utilizza Scheme introduce le estensioni necessarie alle circostanze.
In questo capitolo si vogliono descrivere solo alcuni degli aspetti più importanti di questo linguaggio. Il documento di riferimento è quello citato, ovvero R5RS; alla fine del capitolo si possono trovare anche altri riferimenti per guide più o meno dettagliate su Scheme.
Il linguaggio Scheme prevede dei commenti, che vengono ignorati regolarmente: si distinguono perché iniziano con un punto e virgola (;) e terminano alla fine della riga. Generalmente, le righe vuote e quelle bianche sono ignorate nello stesso modo. In generale, le istruzioni Scheme hanno l'aspetto di qualcosa che è racchiuso tra parentesi tonde.
|
Per comprenderne il senso, l'esempio precedente potrebbe essere espresso come si vede sotto, se lo si volesse rappresentare in un linguaggio ipotetico, basato sulle funzioni:
|
Tutto quello che si fa con Scheme viene ottenuto attraverso chiamate di funzione, ovvero, secondo la terminologia utilizzata da R5RS, procedure, che possono restituire o meno un valore. Le chiamate di queste procedure, o di queste funzioni, iniziano con un nome, posto subito dopo la parentesi tonda di apertura, continuando eventualmente con l'elenco dei parametri che gli vengono passati, separati semplicemente da uno o più spazi, anche verticali (non si utilizzano virgole o altri simboli di interpunzione), terminando con la parentesi tonda di chiusura.
(nome [parametro_1 [parametro_2]... [parametro_n]]) |
Da quanto affermato, si intende anche che un'istruzione può essere interrotta in qualunque punto in cui potrebbe essere inserito uno spazio, per riprenderla nella riga successiva, incolonnandola in base allo stile preferito. Si osservi l'esempio seguente:
|
si tratta di una chiamata a una funzione denominata +, a cui vengono passati i parametri 3 e 4. Si intende, intuitivamente, che questa funzione restituisca la somma dei parametri.
Le istruzioni non hanno bisogno di essere terminate da un qualche simbolo di interpunzione, dal momento che le parentesi tonde esprimono chiaramente l'estensione di queste e l'ambito relativo all'interno dei vari annidamenti. |
Questo tipo di notazione ha diversi pregi, ma ha il difetto fondamentale di essere un po' difficile da seguire visivamente, soprattutto a causa dell'affollarsi delle parentesi tonde.
In questi capitoli si cerca di utilizzare un allineamento di queste parentesi che renda più facile la lettura delle istruzioni, anche se si tratta di uno stile che di solito non si applica. |
Per facilitare la comprensione degli esempi, in questi capitoli dedicati a Scheme, si utilizza il simbolo ===> per indicare il valore restituito da una funzione (che appare alla sua destra). |
I nomi utilizzati per «identificare» qualunque cosa in Scheme, possono essere scritti utilizzando le lettere dell'alfabeto, le cifre numeriche e una serie di caratteri particolari che vengono considerati come un'estensione ai caratteri alfabetici:
! $ % & * + - . / : < = > ? @ ^ _ ~ |
Non tutte le combinazioni sono possibili: in generale non è ammissibile che tali nomi inizino con una cifra numerica.
In generale, Scheme non dovrebbe fare differenza tra lettere maiuscole e minuscole nei nomi che identificano qualcosa. |
È importante osservare che, a differenza di altri linguaggi di programmazione, caratteri come +, -, * e /, possono essere (e in pratica sono) dei nomi.
|
Come è già stato fatto osservare, l'esempio mostra la chiamata della funzione (procedura) +, a cui vengono passati i valori tre e quattro come parametri.
Anche se si possono usare caratteri insoliti nei nomi degli identificatori, quando si dichiara qualcosa, come il nome di una variabile, o di una funzione, è bene astenersi dalle cose troppo stravaganti, a meno che ci sia un buon motivo per le scelte che si fanno. In generale, sono già stabilite delle convenzioni per i nomi delle funzioni, almeno quelle che fanno già parte del linguaggio standard:
le funzioni il cui nome termina con un punto interrogativo (?) sono intese essere dei «predicati», ovvero delle funzioni che verificano l'avverarsi di una condizione (la verità di un'affermazione) e restituiscono un valore booleano;
le funzioni il cui scopo è quello di modificare il valore di una variabile, senza cambiarne l'allocazione (per la precisione si tratta di modificare un valore in un'area di memoria già allocata), terminano con un punto esclamativo (!);
Le funzioni il cui scopo è quello di convertire un «oggetto» di un tipo, in un altro di tipo differente, contengono un -> all'interno del nome.
Per permettere di comprendere meglio come possa essere formato un identificatore, si osservi l'elenco seguente di nomi, che rappresentano tutti delle possibilità valide:
|
Scheme è un linguaggio basato sulle funzioni, per quanto queste vengano chiamate «procedure» nella sua terminologia specifica. Questo significa, per esempio, che tutte le espressioni che si possono scrivere con Scheme sono dei valori costanti, oppure delle chiamate di funzione, più o meno annidate. Anche le strutture di controllo sono realizzate in forma di funzione.
È importante osservare che in Scheme non esiste una funzione principale che debba essere eseguita prima delle altre; si segue semplicemente l'ordine sequenziale in cui appaiono le istruzioni. In generale, con lo stesso criterio, le funzioni che si utilizzano devono essere state dichiarate prima del loro utilizzo.
Scheme utilizza una gestione speciale per le variabili. La dichiarazione di una variabile implica l'allocazione di uno spazio di memoria adatto e l'abbinamento del puntatore relativo a una variabile.
(define variabile [valore_iniziale]) |
L'esempio seguente, alloca l'area di memoria necessaria a contenere un numero intero, quindi abbina all'identificatore x il puntatore a questa area.
|
In pratica, l'identificatore x si comporta come una variabile di un linguaggio di programmazione «normale», dal momento che quando viene valutato in un'espressione restituisce esattamente il valore a cui punta.
Questa distinzione, non è soltanto una questione di pignoleria, ma si tratta di un punto fondamentale della filosofia di Scheme: la dichiarazione successiva dello stesso identificatore, non va a modificare l'informazione precedente, ma alloca una nuova area di memoria. L'allocazione precedente non viene recuperata e potrebbe continuare a essere utilizzata da ciò che è stato dichiarato prima del cambiamento. In questo senso, a livello teorico, il linguaggio Scheme non prevede un sistema di eliminazione degli oggetti inutilizzati (lo spazzino, ovvero il garbage collector), benché le realizzazioni possano attuare in pratica queste forme di ottimizzazione quando sono in grado di provare che un'area di memoria allocata non può più essere presa in considerazione nel programma.
Proprio a causa di questa particolarità di Scheme, per assegnare un valore a un'area di memoria già allocata, attraverso l'identificatore relativo, si utilizza la funzione set!:
(set! variabile espressione_del_valore_da_assegnare) |
Il punto esclamativo finale che compone il nome della funzione, serve a sottolineare il fatto che si ottiene la modifica di un valore già allocato, senza allocare un'altra area di memoria.
I dati secondo Scheme sono organizzati in oggetti, ma non nel senso che viene attribuito dai linguaggi di programmazione a oggetti (object oriented). I tipi di dati di Scheme sono precisamente:
booleano -- inteso come il risultato di un'espressione logica, o una costante booleana;
coppia (lista non vuota);
simbolico -- che fa riferimento a costanti simili alle stringhe, ma che sono trattate diversamente in Scheme;
numerico;
carattere -- un carattere singolo che non è una stringa;
stringa;
vettore -- quello che per gli altri linguaggi è un array;
porta, o flusso -- ovvero un file aperto;
procedura -- le funzioni di Scheme.
I dati hanno una loro essenza e una loro rappresentazione esterna, che corrisponde al modo in cui vengono espressi a livello umano. Questa rappresentazione può consentire a volte l'uso di forme diverse ed equivalenti; per esempio, il numero 16 può essere espresso con la sequenza dei caratteri 16, oppure #d16, #x10 e in altri modi ancora.
Tuttavia, è bene osservare che un oggetto per Scheme può essere di un tipo solo. Si parla in questo senso di «tipi disgiunti». |
Scheme fornisce alcuni predicati, ovvero alcune funzioni, per il controllo del tipo a cui appartiene un oggetto. Nello stesso ordine in cui sono stati elencati i tipi di dati, si tratta di: boolean?, pair?, symbol?, number?, char?, string?, vector?, port?, procedure?. Per esempio, l'istruzione seguente restituisce Vero se l'identificatore x fa riferimento a un numero:
|
Tra tutti i tipi di dati visti, ne esiste uno speciale: la lista vuota, che non appartiene alle coppie. Per identificare una lista di qualunque tipo, includendo anche quelle vuote, si usa il predicato list?.
|
Scheme ha una gestione particolare delle espressioni, dove al loro interno è speciale la gestione dei valori costanti. Questo fatto viene chiarito nel seguito. Tuttavia, è necessario conoscere subito in che modo possono essere indicati i valori più comuni in un sorgente Scheme.
I valori booleani possono essere rappresentati attraverso la sigla #t per Vero e #f per Falso.
I valori numerici possono essere usati nel modo consueto, quando si tratta di valori interi (positivi o negativi), quando si vogliono indicare numeri che hanno una quantità fissa di decimali e quando si usa la notazione scientifica comune (xey).
|
In aggiunta a quello che si può vedere dagli esempi mostrati sopra, si possono indicare dei valori specificando la base di numerazione. Per ottenere questo, si utilizza un prefisso del tipo:
#x |
In questo caso, x è una lettera che esprime la base di numerazione. Segue l'elenco di questi prefissi:
#b -- numero binario;
#o -- numero ottale;
#d -- numero decimale;
#x -- numero esadecimale.
Per esempio, #x10 è equivalente a #d16, ovvero a 16 senza prefissi.
Scheme consente di utilizzare anche altri tipi di notazioni, per indicare alcuni tipi particolari di numeri. Questa caratteristica di Scheme viene descritta più avanti.
Scheme ha una gestione speciale delle espressioni costanti, cosa che viene descritta in seguito. Ugualmente, è prevista la presenza delle stringhe, rappresentate attraverso una sequenza di caratteri delimitata da una coppia di apici doppi: "...".
All'interno delle stringhe è previsto l'uso di sequenze di escape composte dalla barra obliqua inversa (\) seguita da un carattere. Secondo lo standard R5RS è prevista solo la sequenza \", per inserire un apice doppio, e \\, per poter inserire una barra obliqua inversa. Le varie realizzazioni di Scheme, possono prevedere l'utilizzazione di altre sequenze di escape, per esempio come avviene nel linguaggio C.
Potrebbe venire spontaneo l'utilizzo della sequenza \n per inserire il codice di interruzione di riga all'interno di una stringa; tuttavia, anche se potrebbe funzionare, Scheme dispone della funzione newline, che non prevede l'uso di parametri, il cui scopo è quello di fare ciò che serve per ottenere un avanzamento all'inizio della riga successiva. |
|
In Scheme, i caratteri sono qualcosa di diverso dalle stringhe, ma questo vale anche per altri linguaggi di programmazione. Tuttavia, la rappresentazione di una costante carattere è molto diversa rispetto alle stringhe:
#\carattere | #\nome_carattere |
Questi caratteri, sempre secondo Scheme, sono oggetti singoli e non possono essere uniti assieme a formare una stringa, a meno di utilizzare delle funzioni apposite di conversione in stringa. Segue un elenco che mostra alcuni esempi di rappresentazione di questi oggetti carattere.
#\a -- la lettera «a» minuscola;
#\A -- la lettera «A» maiuscola;
#\( -- la parentesi tonda aperta;
#\ -- lo spazio (dopo la barra obliqua inversa c'è esattamente un carattere <SP>;
#\space -- lo spazio, espresso per nome;
#\newline -- il codice di interruzione di riga.
Un'espressione è qualcosa che, per mezzo di una valutazione, fa qualcosa, oppure restituisce un qualche valore, o fa tutte e due le cose. Le espressioni sono cose che riguardano praticamente tutti i linguaggi di programmazione, ma Scheme ha una gestione particolare quando si vuole evitare che qualcosa venga trasformato da una valutazione.
In pratica, in Scheme si distinguono le espressioni letterali, che sono delle espressioni che per qualche ragione, non devono essere elaborate nel modo consueto, ma passate così come sono in modo letterale.
Nella filosofia di Scheme non si hanno delle variabili vere e proprie, ma degli identificatori che fanno riferimento a delle zone di memoria allocate. Tuttavia, si può usare ugualmente il termine «variabile», se si fa attenzione a ricordare la particolarità di Scheme.
La valutazione di una variabile in Scheme genera la restituzione del valore contenuto nell'area di memoria a cui questa punta. Se si usa un interprete Scheme, come quelli descritti nel capitolo introduttivo di questa parte, si può osservare quanto descritto in modo molto semplice:
|
In pratica, l'espressione banale che consiste nell'indicare semplicemente l'identificatore di una variabile, genera la restituzione del valore che in precedenza gli è stato assegnato.
In un linguaggio di programmazione qualunque, le espressioni letterali corrispondono alle costanti letterali, come i numeri, le stringhe e oggetti simili. In Scheme si aggiungono anche altri oggetti.
costante |
'dato |
(quote dato) |
A parte le costanti letterali normali, le altre espressioni letterali si distinguono per essere precedute da un apostrofo iniziale ('), oppure (ed è la stessa cosa), per essere indicate come argomento della funzione quote.
Inizialmente è difficile comprendere il senso di questa notazione. Tuttavia, è importante riconoscere subito che non si tratta di stringhe, in quanto lo scopo per il quale esistono queste espressioni letterali, è proprio quello di evitare che vengano valutate prima del necessario. Si osservino gli esempi seguenti; in particolare, si suppone che esista una variabile a che faccia riferimento a una zona di memoria contenente il valore uno.
|
|
|
|
|
|
|
Nei primi esempi si fa riferimento a qualcosa che si identifica attraverso la lettera «a». (quote a), ovvero 'a, non è un carattere e non è una stringa: è un simbolo non meglio identificato; dipende dal programmatore il significato che questo può avere. Per semplificare le cose, si è immaginato che si trattasse di una variabile.
Tra gli esempi si vede la possibilità di indicare una funzione per la somma, (+ 1 2), come espressione costante. Ci sono situazioni in cui questo è necessario, per esempio quando una funzione deve essere passata come argomento di un'altra, mentre lo scopo non è quello di passare il risultato della valutazione della prima.
Le costanti letterali, come le stringhe, i numeri, i caratteri e i valori booleani, possono essere indicati come espressioni letterali; in tal modo il risultato non cambia, dal momento che la valutazione di tali costanti restituisce le costanti stesse.
Ci sono altri tipi di dati che possono essere indicati in forma di espressioni letterali, ma non sono stati mostrati gli esempi relativi perché questi tipi non sono ancora stati descritti. Tuttavia, il senso non cambia: si usano le espressioni letterali quando non si può lasciare che queste siano valutate.
L'ordine in cui viene valutata un'espressione è relativamente semplice in Scheme, dal momento che non si utilizzano operatori simbolici e tutto è espresso in forma di funzioni. In generale, si valuta prima ciò che sta nella posizione più «interna», venendo mano a mano verso l'esterno.
|
L'esempio appena mostrato si risolve secondo la sequenza di operazioni elencate di seguito:
3 ===> 3
valutazione di (+ 2 4)
2 ===> 2
4 ===> 4
2+4 ===> 6
3*6 ===> 18
Nei linguaggi di programmazione comuni, le espressioni si avvalgono prevalentemente di operatori di vario tipo, tanto che gli operatori sono di per sé delle funzioni, più o meno celate. Con Scheme, questa ambiguità viene eliminata, dal momento che tutte le operazioni di un'espressione si svolgono per mezzo di funzioni. Le funzioni che vengono descritte in queste sezioni, sono quelle che vengono utilizzate più frequentemente nelle espressioni di Scheme.
Il valore restituito da una funzione può essere di tipo diverso a seconda degli operandi utilizzati. Di solito si fa l'esempio della somma di due interi che genera un risultato intero. Scheme ha una gestione particolare dei numeri, almeno a livello teorico, per cui questi vengono classificati in modo molto più sofisticato di quanto facciano i linguaggi di programmazione normali.
Nella sezione dedicata ai numeri, è assente la spiegazione riguardo al tipo numerico «complesso». Eventualmente si può consultare il documento R5RS in cui questo argomento è affrontato. |
Con Scheme, i numeri sono gestiti a due livelli differenti: l'astrazione matematica e la realizzazione pratica. Dal punto di vista dell'astrazione matematica, si distinguono i livelli seguenti:
numero generico;
numero complesso;
numero reale;
numero razionale;
numero intero.
In generale, un numero che appartiene a una classe inferiore, è anche un numero che può essere considerato appartenente a tutti i livelli superiori. Per esempio, un numero razionale è anche un numero reale ed è anche un numero complesso, ecc.
Scheme fornisce una serie di predicati (funzioni), per la verifica dell'appartenenza di un valore a un tipo di numero. L'elenco si vede nella tabella u155.21. In generale, queste funzioni restituiscono il valore Vero (#t) nel caso in cui sia valida l'appartenenza presunta.
|
Nel modo in cui si rappresenta un numero si indica implicitamente il tipo di questo. Tuttavia, se Scheme è in grado di conoscere una semplificazione nel modo di rappresentarne il valore, lo classifica automaticamente nella fascia inferiore relativa. Per esempio, se 4/2 viene mostrato come numero razionale, dal momento che è equivalente a due, è anche un intero puro e semplice. Gli esempi seguenti mostrano in che modo possono reagire i predicati per la verifica del tipo numerico. Si osservi in particolare la disponibilità della notazione m/n, che permette di indicare agevolmente i numeri razionali:
|
|
|
Secondo Scheme, i numeri sono esatti o inesatti, a seconda di varie circostanze, che possono dipendere anche dalla realizzazione che si utilizza. In generale, un numero è esatto se è stato fornito attraverso una costante che di per sé è esatta (come un numero intero o un numero razionale), oppure se deriva da numeri esatti utilizzati in operazioni esatte. Si comprende intuitivamente che nel momento in cui si introducono approssimazioni di qualche tipo, per qualche ragione, i valori che si ottengono dai calcoli che si fanno, non sono precisi, ma sono, appunto, inesatti. Nonostante sia molto facile generare risultati inesatti, anche quando si parte da valori esatti, ci sono alcune situazioni in cui i risultati sono esatti anche se i valori di partenza sono inesatti; per esempio, la moltiplicazione per uno zero esatto, genera uno zero esatto, qualunque sia l'altro valore. A proposito dell'esattezza o meno dei valori, sono disponibili alcune funzioni che sono elencate nella tabella u155.25.
|
Seguono alcuni esempi sull'uso di queste funzioni:
|
Come accennato all'inizio, oltre all'astrazione matematica si pone il problema della precisione dei valori inesatti (quelli che per altri linguaggi di programmazione sono semplicemente dei valori a virgola mobile). Ammesso che la realizzazione di Scheme permetta di distinguere tra diversi livelli di precisione, si possono rappresentare delle costanti numeriche «reali» (a virgola mobile), utilizzando la notazione esponenziale, dove al posto della lettera «e» consueta, si utilizzano rispettivamente le lettere, s, f, d e l, che indicano valori a precisione ridotta (short), a singola precisione (float), a doppia precisione (double) e a precisione ancora maggiore (long).
|
La tabella u155.27 riporta l'elenco delle funzioni più comuni che possono essere usate nelle espressioni aritmetiche e matematiche. In particolare si deve osservare che remainder e modulo si comportano nello stesso modo, tranne quando si utilizzano valori negativi (per approfondire la differenza si può leggere il documento di riferimento su Scheme, ovvero R5RS).
Scheme permette di utilizzare più di due operandi per le funzioni che sommano, sottraggono, dividono e moltiplicano. A parte la spiegazione sintetica data nella tabella in cui sono state presentate, si può intendere il senso del loro funzionamento immaginando che le operazioni avvengono in modo progressivo, da sinistra a destra:
|
L'esempio appena mostrato equivale a:
|
Nello stesso modo, si osservi l'esempio seguente:
|
Questo equivale a:
|
Infine, la tabella u155.32 riporta alcuni predicati utili per classificare in qualche modo un valore numerico.
|
Scheme dispone di altre risorse per la gestione dei valori numerici; inoltre, ciò che è stato presentato qui è descritto in modo approssimativo. Se si vogliono sfruttare bene tali possibilità di questo linguaggio, è indispensabile studiare bene il documento R5RS, già citato più volte, del quale si trova un riferimento alla fine del capitolo. |
Sono già state presentate le costanti booleane #t e #f, che valgono per Vero e Falso rispettivamente. Per Scheme, da un punto di vista logico-booleano, valgono come Vero anche le liste (che vengono descritte in seguito), compresa la lista vuota, i simboli, i numeri, le stringhe, i vettori e le funzioni. In pratica, qualsiasi oggetto diverso dal tipo booleano, assieme al valore booleano #t, vale come Vero, mentre solo #f vale per Falso. Tuttavia, per verificare che un oggetto corrisponda effettivamente a un valore booleano, si può usare il predicato seguente:
(boolean? oggetto) |
Questo restituisce Vero in caso affermativo.
Alcune realizzazioni più vecchie di Scheme trattano la lista vuota, che si rappresenta con (), come equivalente al valore booleano Falso. |
Gli operatori logici sono realizzati in Scheme attraverso funzioni. La tabella u155.33 elenca queste funzioni.
|
Per quanto riguarda il confronto, si distinguono situazioni diverse, a seconda che si vogliano confrontare dei valori numerici, carattere, stringa, oppure che si vogliano confrontare gli «oggetti». Le tabelle u155.34, u155.36, u155.38 e u155.40, riepilogano le funzioni in grado di eseguire tali confronti.
|
È interessante notare che le funzioni per il confronto ammettono l'uso di più di due argomenti. Si osservino gli esempi seguenti, con i risultati che restituiscono:
|
|
Per quanto riguarda il confronto tra caratteri e tra stringhe, non è stabilita la possibilità di inserire più di due argomenti, anche se è possibile che una realizzazione Scheme lo consenta.
|
|
|
Scheme offre dei predicati particolari per il confronto tra due oggetti, come mostrato nella tabella u155.40. È difficile definire in modo chiaro la differenza che c'è tra questi tre predicati. In generale si può affermare che equal? sia il predicato che è più permissivo, mentre eq? è quello più restrittivo.
|
|
Alcune funzioni specifiche per i caratteri sono elencate nella tabella u155.42. Per quanto riguarda il caso particolare del predicato char-whitespace?, questo si avvera nel caso in cui si tratti di <SP>, <HT>, <LF>, <FF> e <CR>.
|
Nella conversione attraverso le funzioni char->integer e integer->char, l'equivalenza tra carattere e numero dipende dalla realizzazione di Scheme; molto probabilmente dipende dalla codifica dell'insieme di caratteri utilizzato.
Alcune funzioni specifiche per i caratteri sono elencate nella tabella u155.43. Quando le funzioni fanno riferimento a un indice per indicare un carattere all'interno di una stringa, si deve ricordare che il primo corrisponde alla posizione zero. Quando si fa riferimento a due indici, uno per indicare il carattere iniziale e uno per fare riferimento al carattere finale, il secondo indice deve puntare alla posizione successiva all'ultimo carattere da prendere in considerazione. Questo permette di individuare una stringa nulla quando l'indice iniziale e l'indice finale sono uguali.
|
|
Anche con Scheme sono disponibili le strutture di controllo comuni nei linguaggi di programmazione. Evidentemente, queste sono realizzate attraverso delle funzioni. In base a tale impostazione, per sottoporre una parte di codice alla verifica di una condizione, o per metterla in un ciclo, occorre che questa sia inserita in una funzione che possa essere chiamata all'interno di un'espressione.
Per intendere il problema, si osservi l'esempio seguente, che mostra la scelta tra la chiamata della funzione display per visualizzare il messaggio «bello», o «brutto», in funzione di una condizione (che in questo caso si avvera necessariamente):
|
Per ovviare a questo inconveniente si può utilizzare la funzione begin, che permette di incorporare più espressioni dove invece se ne potrebbe inserire una sola.
Per tutte le situazioni in cui è possibile indicare una sola espressione, mentre invece se ne vorrebbero inserire diverse, esiste la funzione begin:
(begin espressione espressione ... ) |
Il senso si comprende intuitivamente: le espressioni che costituiscono gli argomenti di begin vengono valutate in ordine, da sinistra a destra (in questo caso dall'alto in basso). L'esempio seguente è molto banale: visualizza un messaggio e termina.
|
È importante osservare che all'interno della funzione begin non è possibile dichiarare delle variabili locali, a meno che per questo si inseriscano delle altre funzioni che creano un loro ambiente, come let e le altre simili. |
La struttura condizionale è il sistema di controllo fondamentale dell'andamento del flusso delle istruzioni.
(if condizione espressione_se_vero [espressione_se_falso]) |
La funzione if valuta i suoi argomenti in un ordine preciso: per prima cosa viene valutato il primo argomento; se il risultato è Vero, o comunque se si ottiene un risultato equiparabile a Vero, valuta il secondo argomento; in alternativa, valuta il terzo argomento, se è stato fornito. Alla fine restituisce il valore dell'ultima espressione a essere stata valutata (ammesso che questa restituisca qualcosa). Sotto vengono mostrati alcuni esempi in cui alcune parti del programma sono state saltate per non distrarre l'attenzione del lettore:
|
|
|
Come accennato, potrebbe essere conveniente l'utilizzo della funzione begin per facilitare la descrizione di gruppi di istruzioni (espressioni). Si osservi l'esempio seguente, in cui viene salvato il valore dell'importo nella variabile Offerta:
|
Scheme fornisce due strutture di selezione. In questo caso, la funzione cond si basa sulla verifica di condizioni distinte per ogni blocco di espressioni.
(cond (condizione espressione...) (condizione espressione...) ... [(else espressione...)] ) |
Lo schema sintattico dovrebbe essere chiaro a sufficienza: la funzione cond ha come argomenti una serie di «blocchi» (si tratta di liste, ma questo viene chiarito quando si passa alla descrizione delle liste), contenenti ognuno un'espressione iniziale che deve essere valutata per determinare se le espressioni successive devono essere valutate o meno. Nel momento in cui si incontra una condizione che si avvera, i blocchi successivi vengono ignorati. Se non si incontra alcuna condizione che si avvera, se esiste l'ultimo blocco, corrispondente alla funzione else, le espressioni relative vengono eseguite.
A differenza della funzione if, in questo caso si possono indicare più espressioni per ogni condizione della selezione; in questo senso, la funzione cond può diventare un sostituto opportuno di quella. Segue un esempio tipico di selezione:
|
Scheme fornisce anche la struttura di selezione tradizionale, ovvero la funzione case, che si basa sulla verifica del valore di una sola «chiave». Anche case permette l'indicazione di più espressioni per ogni elemento della selezione.
(case espressione_di_selezione ((dato...) espressione...) ((dato...) espressione...) ... [(else espressione...)] ) |
La prima espressione a essere valutata è quella che costituisce il primo argomento della funzione case. Successivamente, il suo risultato viene comparato con quello dei «dati» elencati all'inizio di ogni gruppo di espressioni (si vedano gli esempi). Se la comparazione ha successo, allora vengono valutate le espressioni successive (all'interno del blocco), nell'ordine in cui si trovano. Se il confronto non ha successo, se esiste un blocco finale costituito dalla funzione else, vengono eseguite le espressioni relative. Seguono alcuni esempi:
|
|
Scheme dispone di una funzione unica per realizzare i cicli iterativi e quelli enumerativi. Si tratta di do, il cui funzionamento è, a prima vista, un po' strano. Come ciclo iterativo la sintassi si riduce al modello seguente:
(do () (condizione_di_uscita [espressione_pre_uscita...]) espressione_del_ciclo... ) |
In questa forma, viene valutata prima la condizione; se si avvera, vengono valutate le espressioni successive, quelle contenute nello spazio delle parentesi (la lista della condizione), quindi il ciclo termina. Se la condizione non si avvera, vengono eseguite le espressioni esterne al blocco della condizione, al termine delle quali riprende il ciclo.
Quando si vuole usare la funzione do per realizzare un ciclo enumerativo, si definiscono una o più variabili da inizializzare e modificare in qualche modo a ogni ciclo:
(do ((variabile inizializzazione passo)...) (condizione_di_uscita [espressione_pre_uscita...]) espressione_del_ciclo... ) |
Le variabili vengono dichiarate (allocate) dalla funzione do stessa, avendo effetto solo in ambito locale, all'interno della funzione che le dichiara (in pratica, mascherano temporaneamente altre variabili esterne con lo stesso nome). Le variabili vengono inizializzate immediatamente con il valore ottenuto dall'espressione di inizializzazione, quindi inizia il primo ciclo. Alla fine di ogni ciclo, prima dell'inizio del successivo, vengono valutate le espressioni del passo, assegnando alle variabili relative i valori che si ottengono.
L'esempio seguente fa apparire per 10 volte la lettera «x». Si osservi l'uso di una variabile esterna per scandire i cicli:
|
La stessa cosa avrebbe potuto essere ottenuta dichiarando la variabile all'interno della funzione do:
|
Infine, si può trasferire l'incremento del contatore nel blocco in cui si dichiara e si inizializza la variabile Contatore:
|
Un programma Scheme termina quando si esauriscono le istruzioni, oppure quando viene incontrata e valutata la funzione exit.
(exit [valore_di_uscita]) |
Come si vede dallo schema sintattico, è possibile indicare un numero che si traduce poi nel valore di uscita del programma stesso.
L'utilizzo di questa funzione all'interno di un ambiente di interpretazione Scheme, serve normalmente a concludere il funzionamento del programma relativo.
A. Aaby, Scheme Tutorial, 1996
Pierre Castéran, Robert Cori, Passeport pour Scheme
Il documento citato sembra essere scomparso dalla rete, probabilmente in vista di una sua pubblicazione. In origine, si trovava presso http://dept-info.labri.u-bordeaux.fr/~cori/Bouquins/scheme.ps.
R5RS -- Revised-5 Report on the Algorithmic Language Scheme, 1998
http://www.swiss.ai.mit.edu/~jaffer/r5rs_toc.html
http://www.swiss.ai.mit.edu/ftpdir/scheme-reports/r5rs.ps.gz
«a2» 2013.11.11 --- Copyright © Daniele Giacomini -- appunti2@gmail.com http://informaticalibera.net