Lisp – variabili – 1

r1Altro mattone fondamentale, dice Peter: le variabili.
IN Common Lisp abbiamo due tipi di variabili: lessicali e dinamiche (Chiamate anche speciali). Corrispondono approssimativamente alle variabili globali e locali degli altri linguaggi. Ma ormai dovremmo essere abituati che con il Lisp niente è come sembra 🙄
Cominciamo con le cose semplici, poi panico :mrgreen:

Generalità

Come di solito le variabili sono nomi che possono contenere un valore. Qui non sono tipizzate, una var9iabile può contenere qualsiasi tipo di valore e questo può essere esaminato a runtime. Quindi Common Lisp è dynamically typed, gli errori di tipo sono rivelati dinamicamente. Per esempio se si passa qualcosa che non è un numero + segnala l’essere di tipo:

v0

D’altra parte Common Lisp è strongly typed nel senso che ogni errore di tipo viene rilevato (OK, a rigore si potrebbe ma facciamo come le persone normali).
Ogni valore in Lisp è una referenza a un oggetto, quindi assegnare un nuovo valore a una variabile cambia a cosa la variabile si riferisce ma non ha effetto sull’oggetto precedente; a meno che l’oggetto sia mutabile (mutable object), spero che questo venga chiarito dopo.

Un modo per introdurre nuove variabili già visto è quello di definire parametri per una funzione. Definendo una funzione con defun la lista dei parametri definisce le variabili che la funzione contiene quando viene chiamata. Per esempio (defun foo (x y z) (+ x y z)) definisce le 3 variabili x, y e z.
Ogni volta che la funzione è chiamata il Lisp crea nuovi collegamenti (bindings) per contenere gli argomenti passati dal chiamante. Il binding è la manifestazione a runtime della variabile. Ogni volta che si chiama la funzione viene creato un nuovo binding e una stessa variabile può avere contemporaneamente più binding, per esempio nel caso di una funzione ricorsiva.
Come le variabili i parametri di funzione sono referenze a oggetti cosicché puoi assegnare un nuovo valore a un parametro nel corpo della funzione senza che questo alteri il valore per i binding successivi (insomma si passano i valori, direbbe un C-ista). Ma l’oggetto può essere mutabile e allora il cambiamento sarà visibile dal chiamante (passaggio per indirizzo, direbbe il nosto C-ista).

Un altro modo per creare nuove variabili è l’operatore speciale let. Lo schema è il seguente:

(let (variable*)
  body-form*)

dove ogni variable è una initialization form che è o una lista contenente il nome della variabile e il suo valore iniziale o solo il nome della variabile (inizializzata a nil). Il let successivo collega le variabili x, y e z a 10, 20 e nil:

(let ((x 10) (y 20) z)
  ...)

Quando la forma let valutata i valori iniziali vengono valutati per primi, quindi vengono creati i nuovi bindings, inizializzati in accordo con il tipo prima che il corpo (body) della let form sia eseguita. Dentro la let form i nomi si riferiscono alle nuove variabili create, usciti dalla let possono riferirsi a altro, se definiti prima della let.

Il valore dell’ultima espressione nel corpo della let viene ritornato come valore dell’espressione let. Insomma una let è quasi come se fosse una funzione anonima.

Lo scope (portata? vita?) dei parametri di funzione e delle variabili della let –l’area del programma dove possono essere usate– è limitato dalla form che le introduce; questa form è chiamata la binding form. Quindi –dice Peter– si può vedere come i due tipi di variabili –lexical e dynamic– usano due differenti meccanismi di scoping ma in ogni caso lo scope è delimitato dalla binding form. (Se ho capito è un’ovvietà).

Se si annidano binding forms che introducano variabili con lo stesso nome i collegamenti alle variabili più interne oscurano (shadows) i collegamenti a quelli esterni. Nota: cosa che capita in (quasi) tutti i linguaggi che conosco e cosa da non fare, dare nomi sensati alle variabili, cribbio!
Esempio:

(defun foo (x)
  (format t "Parameter: ~a~%" x)      ; |<------ x is argument 
  (let ((x 2))                        ; |
    (format t "Outer LET: ~a~%" x)    ; | |<---- x is 2
    (let ((x 3))                      ; | |
      (format t "Inner LET: ~a~%" x)) ; | | |<-- x is 3
    (format t "Outer LET: ~a~%" x))   ; | |
  (format t "Parameter: ~a~%" x))     ; |

v1

Ci sono altri costrutti che definiscono variabili (prossimamente), ognuno nella propria binding form.
Per esempio il ciclo dotimes. Introduce una variabile contatore che contiene il valore del ciclo. Ecco come contare fino a 9:

v2

Un’altra binding form è let*, variante di let. A differenza di let in cui le variabili possono essere usate solo nel corpo della form con let* i valori iniziali delle variabili possono riferirsi a variabili introdotte precedentemente nella lista delle variabili, esempio:

v3

OK, cosa che non posso farlo con let:

v4

Tuttavia si può avere lo stesso risultato con let annidati:

v5

Pausa? 🙄 OK 😀

Posta un commento o usa questo indirizzo per il trackback.

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: