# Scheme

Scheme è un linguaggio di programmazione funzionale, un dialetto del Lisp di cui mantiene tutte le caratteristiche, che è stato sviluppato negli anni settanta da Guy L. Steele e Gerald Jay Sussman, che lo introdussero nel mondo accademico con una serie di articoli noti come le Lambda Papers e nel libro Structure and Interpretation of Computer Programs. Il desktop manager GNOME incorpora l'interprete Scheme Guile.

## Programmazione

A differenza della maggior parte degli altri linguaggi di programmazione, Scheme utilizza una notazione prefissa, ovvero una notazione in cui al posto di scrivere (2 + 3) si scrive (+ 2 3). Questa notazione si propaga a tutte le funzioni, sicché se abbiamo una funzione N-aria f, la sua rappresentazione sarà (f argomento1 argomento2... argomentoN).

### Tipi di dato

Lo Scheme implementa tutti i tipi di dato fondamentale: booleani, numeri, caratteri, stringhe, vettori. Tuttavia include anche tipi speciali, tra cui le liste (coppie), le porte (flussi di dati), i simboli e le procedure.
Per riconoscere questi tipi di dato, Scheme mette a disposizione delle particolari funzioni il cui identificatore ha il formato "tipoDiDato?" che restituiscono il booleano TRUE se l'argomento passato è nel formato tipoDiDato. Ecco una tabella riassuntiva:
| Funzione | Spiegazione |
| (boolean? argomento) | l'argomento è un booleano? |
| (number? argomento) | l'argomento è un numero? |
| (char? argomento) | l'argomento è un carattere? |
| (string? argomento) | l'argomento è una stringa? |
| (vector? argomento) | l'argomento è un vettore? |
| (list? argomento) | l'argomento è una lista? |
| (port? argomento) | l'argomento è una porta? |
| (symbol? argomento) | l'argomento è un simbolo? |
| (procedure? argomento) | l'argomento è una procedura? |
Vediamo ora alcuni dettagli su questi tipi di dati.

#### Numerici

Le costanti numeriche si scrivono secondo le usuali regole. Sono disponibili vari tipi di dato numerico: numeri positivi e negativi, numeri con la virgola, numeri in notazione esponenziale e numeri complessi. Per riconoscere a che classe di numeri appartiene un'espressione numerale, è possibile usare una delle seguenti funzioni, nuovamente designate da un identificatore con il punto interrogativo, che restituiscono un valore booleano:
| Funzione | Risultato restituito |
| (integer? argomento) | l'argomento è un intero |
| (rational? argomento) | l'argomento è un numero razionale |
| (real? argomento) | l'argomento è un reale |
| (complex? argomento) | l'argomento è un numero complesso |
È inoltre possibile specificare la base di numerazione tramite #fN, dove f è la base ed N un numero:
| Basi possibili | Tipo di base |
| #bN | N è binario |
| #oN | N è ottale |
| #dN | N è decimale |
| #xN | N è esadecimale |
* dN non si usa praticamente mai, visto che la base è di default quella decimale.
Esistono poi le seguenti funzioni booleane (punto interrogativo) e di conversione:
| Funzioni | funzionalità |
| (exact? espressione) | L'espressione restituisce un numero esatto? |
| (inexact? espressione) | L'espressione restituisce un numero approssimato? |
| (exact->inexact espressione) | Approssima il valore dell'espressione |
| (inexact->exact espressione) | Converte un valore approssimato in un valore esatto. |
Per classificare ulteriormente i numeri, ad esempio tra positivi e negativi, o pari e dispari, Scheme mette a disposizione le seguenti primitive:
| Funzioni | funzionalità |
| (zero? numero) | Il numero è uno zero? |
| (positive? numero) | Il numero è positivo? |
| (negative? numero) | Il numero è negativo? |
| (odd? numero) | Il numero è dispari? |
| (even? numero) | Il numero è pari? |
Ovviamente poi, come nella maggioranza dei linguaggi, si hanno a disposizione sia gli operatori aritmetici che molte funzioni matematiche.
| Funzioni | Risultato restituito | tipo |
| (+ arg1 arg2 ... argN) | arg1+arg2+...+argN | numerico |
| (- arg1 arg2 ... argN) | arg1-arg2-...-argN | numerico |
| (* arg1 arg2 ... argN) | arg1*arg2*...*argN | numerico |
| (/ arg1 arg2 arg3... argN) | (..(arg1/arg2)/arg3).../argN | numerico |
| (log arg) | log naturale di arg | numerico |
| (exp arg) | esponenziale di arg | numerico |
| (sin arg) | seno di arg | numerico |
| (cos arg) | coseno di arg | numerico |
| (tan arg) | tangente di arg | numerico |
| (asin arg) | arcoseno di arg | numerico |
| (acos arg) | arcocoseno di arg | numerico |
| (atan arg) | arcotangente di arg | numerico |
| (sqrt arg) | radice quadrata arg | numerico |
| (expt base potenza) | base^potenza | numerico |
| (abs arg) | valore assoluto di arg | numerico |
| (quotient arg1 arg2) | risultato intero della divisione arg1/arg2 | numerico |
| (remainder arg1 arg2) | resto (positivo) della divisione | numerico |
| (modulo arg1 arg2) | resto della divisione | numerico |
| (ceiling arg) | tetto di arg (approssimazione della parte intera per eccesso) | numerico |
| (floor arg) | pavimento di arg (approssimazione della parte intera per difetto) | numerico |
| (round arg) | approssimazione generica di arg | numerico |
| (truncate arg) | troncamento di arg | numerico |
| (max arg1 ... argN) | valore massimo tra arg1 e argN | numerico |
| (min arg1 ... argN) | valore minimo tra arg1 e argN | numerico |
| (gcd arg1 ... argN) | massimo comune divisore tra arg1..e.. argN | numerico |
| (lcm arg1 ... argN) | minimo comune multiplo tra arg1.. e.. argN | numerico |
| (numerator arg) | numeratore di arg | numerico |
| (denominator arg) | denominatore di arg | numerico |
| (= arg1 arg2 ... argN) | arg1=arg2=...=argN? | booleano |
| (< arg1 arg2 ... argN) | arg1<arg2<...<argN? | booleano |
| (> arg1 arg2 ... argN) | arg1>arg2>...>argN? | booleano |
| (>= arg1 arg2 ... argN) | arg1>=arg2>=...>=argN? | booleano |
| (<= arg1 arg2 ... argN) | arg1<=arg2<=...<=argN? | booleano |

#### Caratteri

In Scheme i caratteri si indicano con un cancelletto, seguito da un backslash ed il carattere che si vuole esprimere (senza il backslash, ci sarebbe ambiguità tra i caratteri T e F ed i booleani TRUE e FALSE).
Tra le funzioni rilevanti troviamo:
| Funzioni | Risultato restituito |
| (char=? char1 char2) | char1 è uguale a char2? |
| (char<? char1 char2) | char1 è lessicograficamente inferiore a char2? |
| (char>? char1 char2) | char1 è lessicograficamente superiore a char2? |
| (char<=? char1 char2) | char1 è lessicograficamente non superiore a char2? |
| (char>=? char1 char2) | char1 è lessicograficamente non inferiore a char2? |
Di queste funzioni esiste anche la versione ci, case insensitive: si ha quindi char-ci=?, char-ci<?, char-ci<=?, e così via.
Altre funzioni operanti sui caratteri sono le seguenti:
| Funzioni | Risultato restituito | tipo |
| (char? arg) | arg è un carattere? | booleano |
| (char-alphabetic? arg) | arg è un carattere alfabetico? | booleano |
| (char-numeric? arg) | arg è un carattere rappresentante un numero? | booleano |
| (char-whitespace? arg) | arg è un carattere rappresentante uno spazio bianco? | booleano |
| (char-upper-case? arg) | arg è un carattere alfabetico maiuscolo? | booleano |
| (char-lower-case? arg) | arg è un carattere alfabetico minuscolo? | booleano |
| (char->integer arg) | il carattere viene trasformato in numero | numerico |
| (integer->char arg) | il numero viene trasformato in carattere | carattere |
| (char-upcase arg) | il carattere alfabetico diventa maiuscolo | carattere |
| (char-downcase arg) | il carattere alfabetico diventa minuscolo | carattere |

#### Stringhe

Le stringhe in scheme si rappresentano tra doppi apici (esempio: "questa è una stringa"). Alcune funzioni per la gestione delle stringhe, sono le seguenti:
| Funzioni | Risultato restituito | tipo |
| (make-string n) | crea una stringa lunga n | stringa |
| (make-string n ch) | crea una stringa lunga n, formata dal solo carattere ch | stringa |
| (string char1 char2 ..charN) | crea una stringa formata dai caratteri char1, char2,... charN | stringa |
| (string-length str) | restituisce la dimensione di str | numerico |
| (string-ref str idx) | restituisce il carattere alla posizione idx, della stringa str | carattere |
| (substring str beg end) | restituisce la porzione della stringa str contenuta tra i due indici beg ed end | stringa |
| (string-set! str idx ch) | sostituisce con ch, il carattere alla posizione idx nella stringa str | stringa |
| (string-append str1 str2...strN) | concatena le stringhe str1, str2, ..., strN | stringa |
| (string-copy str) | restituisce una copia della stringa str | stringa |
| (string->list str) | restituisce una lista formata dai caratteri della stringa str | lista |
| (list->string lst) | restituisce una stringa formata dai caratteri della lista lst | stringa |
Come per i caratteri abbiamo degli operatori di confronto tra stringhe, che sono analoghi a quelli per i caratteri. I principali sono elencati in seguito, ed esistono anche nella versione string-ci (case insensitive):
| Funzioni | Risultato restituito |
| (string=? str1 str2) | str1 è uguale a str2? |
| (string<? str1 str2) | str1 è lessicograficamente inferiore a str2? |
| (string>? str1 str2) | str1 è lessicograficamente superiore a str2? |
| (string<=? str1 str2) | str1 è lessicograficamente non superiore a str2? |
| (string>=? str1 str2) | str1 è lessicograficamente non inferiore a str2? |

#### Liste

Come specificato prima, le liste sono un particolare tipo di dato. Come gli array, rappresentano collezioni ordinate di elementi; a differenza degli array, gli elementi possono essere eterogenei (di tipi differenti fra loro), e inoltre non possono essere indicizzati.
Le liste sono realizzate come coppie: (2 3) rappresenta un esempio di lista che è evidentemente una coppia; ma anche (2 3 4) è in realtà una coppia, formata dal primo elemento (2) e da tutti gli altri (3 4). A sua volta, l'elemento "tutti gli altri" è una coppia formata dal suo primo elemento (3) e da tutti gli altri (in questo caso solo il 4). La lista è quindi descritta in modo molto naturale in termini ricorsivi.
Una lista può contenere qualunque tipo di dato, come caratteri, stringhe, booleani, e anche altre liste (esempio:"(2 3 (4 5))"); come già detto, possono anche contenere tipi di dato misti (esempio:"(#\T 4 (4 #F ("stringa")))").
Le due funzioni fondamentali per agire sulle liste si rifanno alla definizione ricorsiva accennata sopra: abbiamo così la funzione car, che restituisce il primo elemento, e cdr, che restituisce il secondo elemento (l'elemento "tutti gli altri").
Le principali funzioni che Scheme fornisce per le liste sono le seguenti:
| Funzioni | Risultato restituito | tipo |
| (list arg1 arg2 ... argN) | costruisce una lista formata dagli argomenti arg1,arg2,...,argN | lista |
| (pair? lst) | la lista lst è non vuota? | booleano |
| (car lst) | restituisce il primo elemento della lista lst | misto |
| (cdr lst) | restituisce la lista formata dagli elementi dal secondo all'ultimo elemento della lista lst | lista |
| (cadr lst) | restituisce il primo elemento della lista che si otterrebbe invocando (cdr lst) | misto |
| (cons arg lst) | restituisce una lista in cui al primo posto c'è arg | lista |
| (append lst1 lst2) | concatena le due liste lst1 e lst2 | lista |
| (length lst) | restituisce il numero di elementi della lista | numerico |
| (reverse lst) | inverte gli elementi della lista | lista |
Notiamo che in questo elenco sono disponibili funzioni aggregate che corrispondono alla composizione di car e cdr: l'espressione (cdr(cdr lst)) può essere scritta in modo più sintetico come (cddr lst), mentre (car (cdr lst)) si può sintetizzare con (cadr lst).
Scheme presenta poi altre funzioni composte, alcune delle quali simulano le funzioni sui vettori, ed altre la conversione lista a vettore e viceversa:
| Funzioni | Risultato restituito | tipo |
| (null? lst) | la lista lst è vuota? | booleano |
| (set-car! lst arg) | setta la prima posizione al valore arg | lista |
| (set-cdr! lst arg) | setta la seconda posizione al valore arg | lista |
| (list-ref lst k) | restituisce l'elemento nella posizione k della lista lst | misto |
| (list->vector lst) | restituisce un vettore formato dagli elementi della lista lst | vettore |
| (vector->list vctr) | restituisce una lista formata dagli elementi vel vettore vctr | vettore |
Funzioni particolari che agiscono sulle liste sono le funzioni apply, map e for-each:
| Funzioni | Risultato restituito |
| (apply f lst) | esegue la funzione f usando come elementi quelli presenti nella lista |
| (map f lst1...lstN) | esegue la funzione f sugli elementi allo stesso indice della lista |
| (for-each f lst1...lstN) | esegue la funzione f sugli elementi delle liste |
Per spiegare meglio, facciamo un esempio:

#### Vettori

I vettori sono degli array standard, ovvero sono una struttura che contiene un solo tipo di dato. Si rappresentano come liste con un cancelletto prefisso, ovvero #(el1 el2... elN), dove el1 ha indice 0, ed elN ha indice N-1.
Le funzioni che Scheme fornisce per la gestione dei vettori sono le seguenti:
| Funzioni | Risultato restituito | tipo |
| (vector arg1 arg2 ... argN) | costruisce un vettore formato dagli elementi arg1, arg2,..., argN | vettore |
| (make-vector n) | costruisce un vettore formato da n elementi vuoti | vettore |
| (make-vector n arg) | costruisce un vettore formato da n elementi di tipo arg | vettore |
| (vector-length vctr) | restituisce il numero di elementi del vettore vctr | numerico |
| (vector-ref vctr idx) | restituisce l'elemento nella posizione idx del vettore vctr | vettore |
| (vector-set! vctr idx arg) | imposta l'elemento del vettore vctr, all'indice idx al valore arg | vettore |

#### Porte

Le porte sono oggetti che rappresentano flussi di dati. Sulle porte sono disponibili numerose funzioni, tra le quali troviamo la lettura/scrittura dallo standard input/output (lettura/scrittura di caratteri da/nel terminale, analogamente alle funzioni printf e scanf nel linguaggio C) e la gestione di file. Le porte si possono aprire in input (lettura di dati) ed in output (scrittura di dati).
Alcune delle funzioni fornite dallo Scheme sono le seguenti:
| Funzioni | Risultato restituito | tipo |
| (input-port? arg) | arg è una porta in input? | booleano |
| (output-port? arg) | arg è una porta in output? | booleano |
| (open-input-file str) | apre un file il cui nome è la stringa str come porta in input | porta |
| (open-output-file str) | apre un file il cui nome è la stringa str come porta in output | porta |
| (close-input-file str) | chiude un file il cui nome è la stringa str, aperto in input | niente |
| (close-output-file str) | chiude un file il cui nome è la stringa str, aperto in output | niente |

##### Lettura dei dati

Le letture di dati, dette "operazioni di input", avvengono tramite le seguenti funzioni:
| Funzioni | Risultato restituito | tipo |
| (read-char port) | restituisce il carattere successivo dalla porta port | carattere |
| (peek-char port) | restituisce il carattere successivo dalla porta port (ma non sposta il puntatore) | carattere |
| (read port) | legge dalla porta port | misto |
| (eof-object port) | siamo alla fine del file? | booleano |
Nota: se su read-char e read non si specifica la porta port, la lettura avviene dallo standard input (lettura da console).

##### Scrittura dei dati

Le scritture di dati, dette "operazioni di output", avvengono tramite le seguenti funzioni:
| Funzioni | Risultato restituito |
| (write-char char port) | scrive il carattere char sulla porta port |
| (write obj port) | scrive l'oggetto obj sulla porta port |
| (display obj port) | mostra l'oggetto obj attraverso la porta port |
| (newline port) | scrive una linea vuota sulla porta port |
Nota: se su write-char e write non si specifica la porta port, la scrittura avviene sullo standard output (scrittura su console).

### Definizione di procedure
#### Definizione di un dato

In Scheme la definizione di un dato di qualsiasi tipo avviene tramite la funzione define:
Anche per le procedure si usa quindi define. Il metodo più semplice per creare una procedura che prenda un certo numero di argomenti ha la struttura seguente:
In Scheme è disponibile la funzione lambda per creare procedure senza nome. Essa ci fornisce una modalità alternativa per definire una procedura, creando una procedura anonima e assegnandola, come se fosse un valore convenzionale, a una variabile.
Definizione di una procedura, tramite la funzione lambda:
Lambda permette anche la definizione di procedure all'interno di altre procedure.

#### Costrutti fondamentali
##### Condizione semplice (if)

Dopo aver visto i tipi di dato e le funzioni per operare su di essi, e dopo aver capito come si definisce una procedura, passiamo ai costrutti fondamentali.
Il costrutto if si presenta nel seguente modo:
Si potrebbe cioè dire che l'if di Scheme contiene in sé anche l'else degli altri linguaggi di programmazione.

##### Condizione composta (cond)

È equivalente a una catena di multipli if, e si presenta nel seguente modo:
Ci sono un paio di cose da notare:
* l'else non è obbligatorio: se le condizioni esplicitate sono esaustive, cioè coprono il 100% dei casi possibili, si può omettere. Al contrario, in assenza di else, nel caso in cui nessun caso è verificato si genererà un errore di Run-time.
* Si tratta di una funzione derivata, implementabile tramite l'if ed il costrutto begin.

##### Condizione basata sul valore restituito da un'espressione (case)

Si tratta di un'espressione soggetta a una condizione, dal cui risultato dipende il valore assunto dall'espressione stessa. I possibili valori sono specificati nella struttura come val1, val2, val3:
Come nel caso del costrutto cond, l'else è opzionale.

##### Blocchi di espressioni (begin)

Per esprimere un blocco di espressioni in Scheme si usa la funzione begin:
Questo permette di specificare più espressioni (quelle racchiuse dal begin) in un punto dove sintatticamente occorre un'espressione unica. Un esempio tipico è negli if.

##### Un costrutto non fondamentale (do)

Essendo un linguaggio funzionale, in Scheme sarebbe bello poter usare sempre l'if e la ricorsione. Per quanto sia possibile nella teoria, questa soluzione non sempre aiuta la leggibilità, né dà luogo necessariamente alla soluzione algoritmicamente più efficiente o naturale. Scheme mette quindi a disposizione il costrutto do, che consente di eseguire cicli. In questo, il costrutto do svolge un ruolo simile ai for e ai while di altri linguaggi di programmazione.
Il do si presenta sotto diverse forme. La prima, che assomiglia al costrutto while di java, è la seguente:
Si guarda la condizione di uscita: se è vera vengono valutate sequenzialmente le espressioni successive (prima_espressione_del_ciclo, seconda_espressione_del_ciclo... n-esima_espressione_del_ciclo). L'esecuzione viene quindi iterata, con un nuovo controllo della condizione di uscita. Quando questa fallisce, viene eseguita l'espressione (opzionale) che la segue, e poi il ciclo termina.
La seconda forma assomiglia molto ai generici costrutti for:
La variabile viene inizializzata ad un certo valore; ad ogni iterazione successiva, la variabile viene aggiornata eseguendo l'espressione_di_aggiornamento, che può prevedere un incremento o decremento della variabile, ma anche un'operazione più generale. Per ogni altro aspetto l'esecuzione avviene come nel caso precedente, dove tuttavia condizione_di_uscita viene tipicamente soddisfatta a seconda dei valori assunti dalla variabile di ciclo.

#### Programmare in Scheme

Come già notato, Scheme è particolarmente adatto a esprimere gli algoritmi in forma ricorsiva.
La ricorsione semplice si ottiene richiamando un'unica volta la procedura stessa. Supponiamo ad esempio di non avere a disposizione l'operatore *, e di voler definire la moltiplicazione tra interi per addizioni successive. Visto che k*n equivale a k+k+...+k, con k ripetuto n volte, allora potremo scrivere il seguente codice:
In questo esempio la funzione molt viene richiamata all'interno del suo stesso codice.
Come funziona la ricorsione? Supponiamo di avere (molt 3 4): il risultato atteso è 3*4=12. Vediamo da vicino il flusso di esecuzione:
Un secondo tipo di ricorsione prevede una ricorsione che ad ogni iterazione può eseguire chiamate diverse, a seconda delle condizioni che si verificano. Vediamone un esempio tramite successione di Fibonacci.
Anche se è possibile descrivere il flusso d'esecuzione come sopra, questo cresce molto in fretta (vedere la Teoria della complessità computazionale), e quindi non verrà riportato.

### Esempi vari di programmazione in Scheme

Un esempio di programma che interagisce con l'utente:
Implementazione del problema del tavolo rotondo (o di Cesare):
Un esempio più complesso, il gioco Tic Tac Toe (semplice implementazione ASCII):