| « Le cinque fasi della fine di un fanboy Apple | Un'introduzione a Scheme #1 » |
Nella prima puntata di questa serie di articoli abbiamo parlato di come appaiono le espressioni in Scheme. Oggi andremo un po' più in profondità e presenteremo qualche nozione utile a creare programmi che effettivamente fanno qualcosa. In effetti, oggi sarà svelato parte del mistero riguardante le parentesi, e come mai questa caratteristica rende Scheme uno dei linguaggi più avanzati in circolazione.
Follow up:
Dentro le espressioni
Come abbiamo visto, un'espressione Scheme è delimitata da una coppia di parentesi tonde, all'interno delle quali troviamo una lista di uno o più elementi (ad esempio (+ 1 2)). Questi elementi possono essere di varia natura: possono essere espressioni atomiche, come ad esempio i numeri, oppure possono essere altre chiamate a funzione o forme speciali. Ma cosa sono, in realtà, le chiamate a funzione? Il mistero è presto svelato: si tratta di liste, ovvero della struttura dati fondamentale di Scheme. Se il primo elemento della lista è il nome di una funzione, essa viene richiamata sui restanti elementi. Per fare un paragone con il Python, è come se al posto di scrivere f(a, b, c) poteste scrivere [f, a, b, c]. La domanda che sorge spontanea a questo punto è: ma se quando scrivo una lista essa viene interpretata come una chiamata a funzione, allora come faccio a scrivere un'espressione il cui valore è proprio una lista?
La forma speciale quote
Nella precedente puntata ho detto che il compilatore valuta gli argomenti di una chiamata a funzione partendo dalla funzione più interna per arrivare alla più esterna. Ora, come abbiamo visto, in certi casi la valutazione di un elemento non comporta comportamenti inaspettati: i numeri, ad esempio, vengono valutati come loro stessi. Tuttavia, come abbiamo visto, le liste vengono valutate in chiamate a funzione verso il primo elemento anziché come loro stesse. Per ovviare a questo comportamento, esiste una forma speciale chiamata quote, che non fa altro che valutare i suoi argomenti così come sono scritti, senza applicare chiamate o trasformazioni di alcun genere; ad esempio, se provate a scrivere (quote (+ 1 2)) nel vostro interprete vedrete che l'espressione viene valutata nella lista composta dagli atomi +, 1 e 2, mentre se scrivete (+ 1 2) il codice verrà valutato come l'atomo 3. L'operatore quote può essere applicato a qualsiasi elemento o gerarchia di elementi, ma nella maggior parte dei casi non è necessario: nel caso di espressioni che già vengono valutate come loro stesse l'applicazione di quote non ha effetto, e quindi ad esempio (quote 5) viene valutata esattamente come l'atomo 5.
Scrivere quote e una coppia di parentesi in più ogni volta che si vuole indicare una lista, tuttavia, è scomodo, e per questo esiste una versione più comoda, che consiste nell'anteporre un apice davanti all'espressione da citare letteralmente: il codice 'argomento viene automaticamente espanso in (quote argomento), consentendo di risparmiare tempo e mal di testa dovuto alle troppe parentesi.
L'ultima considerazione da fare su quote è sulla valutazione di liste nidificate. Supponiamo di valutare l'espressione '(3 (+ 5 2)); a prima vista potremmo pensare di ottenere una lista contenente i valori 3 e 7, ma non è così. L'operatore quote, infatti, prende alla lettera tutto ciò che costituisce il suo argomento, incluse eventuali liste nidificate, e quindi non otterremmo altro che una lista il cui primo elemento è l'atomo 3 e il secondo elemento è un'altra lista contenente gli atomi +, 5 e 2. Fortunatamente Scheme ci mette a disposizione un altro operatore, list, che ci consente di ottenere quanto desiderato, ovvero una lista dei suoi argomenti dopo averli valutati con le regole "classiche". Con riferimento all'ultimo esempio, per ottenere la lista con i valori 3 e 7 si sarebbe dovuto utilizzare il codice (list 3 (+ 5 2)).
Un po' di attrezzi del mestiere
Apriamo ora una piccola parentesi per parlare di alcune funzioni e forme speciali utili. Il primo è define, che serve a definire una nuova variabile con un valore da noi scelto. Per definire una variabile x di valore 3, ad esempio, è sufficiente scrivere (define x 3). Se successivamente, vogliamo cambiare il valore alla variabile, possiamo usare l'operatore set!, che funziona in modo del tutto simile; ad esempio, il codice (set! x 1) imposta il valore di x a 1.
Passando alle liste, prendiamo in esame le tre funzioni cons, car e cdr.
La prima, cons, costruisce una nuova lista a partire da un valore e da una lista preesistente. Il nuovo valore viene inserito in cima alla lista, ovvero (cons 1 '(2 3)) viene valutato come (1 2 3). Costruire una lista a partire da un valore e dalla lista vuota corrisponde a creare una lista di un solo elemento, cioè (cons 1 '()) viene valutato come (1).
Le funzioni car e cdr, invece, restituiscono rispettivamente il primo elemento di una lista, e tutti quelli rimanenti; per questo motivo sono conosciuti anche con i nomi di first e rest. Il codice (car '(1 2 3)) viene valutato come l'atomo 1, mentre (cdr '(1 2 3)) come la lista (2 3).
Lambda e le funzioni
Introduciamo adesso brevemente l'operatore più importante di tutto il linguaggio: lambda. Oggi ci limiteremo a una brevissima introduzione, ma le cose da dire sarebbero veramente tantissime. L'operatore lambda serve per creare una nuova funzione, che può essere poi assegnata a una variabile ed utilizzata in altri contesti. La sua sintassi è la seguente:
Scheme:
| (lambda (argomento1 argomento2 ...) | |
| codice) |
Ovviamente è possibile inserire un qualsiasi numero di argomenti, che potranno essere utilizzati all'interno del codice della funzione. Supponiamo di voler creare una funzione f che prende due argomenti e poi somma il doppio del primo al secondo, ovvero che restituisca 11 a partire dall'espressione (f 5 1). Con la notazione lambda, questo si traduce in:
Scheme:
| (define f | |
| (lambda (x y) | |
| (+ (* 2 x) y))) |
Nel caso che si voglia assegnare la funzione generata a una variabile, esiste una versione di define che combina l'operatore lambda al suo interno, con una sintassi più concisa. Con questa agevolazione, il codice dell'ultimo esempio diventa:
Scheme:
| (define (f x y) | |
| (+ (* 2 x) y)) |
L'operatore lambda consente di creare molto più di questo, ma di questo parleremo nelle prossime puntate.
Per finire l'articolo, ecco svelato il mistero delle parentesi. Scrivendo tutto il codice come una gerarchia di liste, di fatto si annulla la differenza tra codice e dati. Un programma, infatti, può essere considerato un dato da parte di un altro programma, o addirittura da se stesso, e questo porta a un traguardo quasi fantascientifico: in Scheme è agevole scrivere programmi che scrivono a loro volta codice sorgente, o che modificano la loro stessa struttura. Questa capacità viene sfruttata in una miriade di campi diversi per creare con semplicità oggetti molto complessi, come scopriremo nelle prossime puntate. Nel frattempo dovreste avere abbastanza strumenti per iniziare a giocare con Scheme e divertirvi un po'.
Stay tuned.
Trackback address for this post
Trackback URL (right click and copy shortcut/link location)
No feedback yet
Comments are not allowed from anonymous visitors.
Recent comments