Ambiente (programmazione)
Un ambiente, in informatica e nell'ambito della programmazione, è definito come l'insieme di tutte le associazioni tra identificatori e posizioni di memoria. Più formalmente, l'ambiente è una funzione che associa il dominio semantico degli identificatori (Id
) a quello dei valori denotabili da identificatori (valori denotabili, DVal
), quali ad esempio le posizioni di memoria, le procedure, le funzioni:
Env = Id DVal
Si noti che, poiché le posizioni di memoria contengono dei valori, esiste un'ulteriore funzione, detta store, che lega il dominio delle posizioni di memoria (Loc
, sottoinsieme di DVal
) in quello dei valori memorizzabili SVal
, come ad esempio un intero o un file di input o output:
Store = Loc SVal
Ovviamente l'associazione tra identificatore e valore associato, come ad esempio in una variabile, necessita dell'applicazione in sequenza delle due funzioni:
dove
i: identificatore
ρ: ambiente
l: posizione
σ: store
v: valore
In definitiva, si può dire che gli identificatori fanno riferimento, tramite l'ambiente, ad una posizione, ed a questa viene associato un valore, che può variare dinamicamente, attraverso lo store.
Struttura dell'ambiente
modificaLa definizione formale di ambiente introduce due situazioni particolari a cui gli identificatori possono sottostare:
- sharing in cui uno stesso identificatore ha due diversi significati, dipendentemente dal contesto in cui viene usato;
- aliasing in cui due identificatori diversi fanno riferimento alla stessa posizione, e quindi allo stesso valore.
Generalmente, le dichiarazioni possono modificare ambiente e store, mentre i comandi modificano solo lo store. Una dichiarazione infatti introduce un nuovo legame tra un identificatore ed una posizione, ed eventualmente modifica lo store associando un valore alla posizione; un comando può modificare il contenuto delle posizioni, ma non l'associazione tra identificatore e posizione (tranne in pochi linguaggi che ne consentono la manipolazione diretta).
Per semplicità è possibile immaginare l'ambiente come l'insieme di tutti i nomi visibili in un certo momento durante l'esecuzione di un programma; informalmente, quindi, possono far parte dell'ambiente le variabili, le procedure, le costanti, le classi.
L'elemento significativo per la gestione dell'ambiente è il sottoprogramma, o blocco, che ne rappresenta quindi l'unità basilare di gestione. L'ambiente visibile (attivo) all'interno di un sottoprogramma è strutturato nel modo seguente:
- ambiente locale, ossia le associazioni create all'ingresso del sottoprogramma; ne sono un esempio i parametri formali di funzioni o procedure o le dichiarazioni locali;
- ambiente globale, ossia le associazioni comuni a tutti i sottoprogrammi, quali le dichiarazioni presenti nel blocco più esterno, o quelle esportate da moduli;
- ambiente non locale, ossia le associazioni ereditate da altri sottoprogrammi secondo le regole di scoping.
Per semplicità, facciamo riferimento ai soli linguaggi imperativi, ricordando che per i linguaggi funzionali possono essere fatte considerazioni analoghe. Possiamo immaginare una struttura a blocchi come segue:
begin // inizio programma <dichiarazioni> <comandi> begin // inizio blocco 1 <dichiarazioni> <comandi> end // fine blocco 1 <comandi> begin // inizio blocco 2 <dichiarazioni> <comandi> begin // inizio sottoblocco 2.1 <dichiarazioni> <comandi> end // fine sottoblocco 2.1 end // fine blocco 2 end // fine programma
Ogni sottoblocco può quindi avere delle proprie dichiarazioni (di variabili, funzioni, classi, ecc) nonché dei comandi che sono ovviamente dipendenti dal linguaggio impiegato.
Si noti che non tutti i linguaggi di programmazione supportano questi tre tipi di ambiente (ad esempio il Prolog non possiede un ambiente non locale).
Ambiente globale
modificaInsieme di associazioni che vengono create nella parte più esterna del programma, e che normalmente sono visibili all'interno di ogni sottoprogramma o sottoblocco. In alcuni linguaggi possono essere visibili automaticamente, all'interno dei sottoblocchi, solo le associazioni tra identificatori e nomi di procedura o funzione, ma non tra identificatori e posizioni, a meno che ciò non venga esplicitamente richiesto (come accade, ad esempio, nelle funzioni in PHP).
Ambiente locale
modificaInsieme di associazioni che vengono create (o attivate) quando si entra in un sottoprogramma. Ad esempio, in Perl:
my $x=3; # dichiarazioni per l'ambiente globale my $y=$x; { # inizio sottoblocco my $z="foo"; # dichiarazioni per l'ambiente locale my $x=777; $y="$y$z$x"; } print "y vale $y, x vale $x\n"; print (defined($z) ? "z vale $z\n" : "z non esiste\n"); OUTPUT> y vale 3foo777, x vale 3 OUTPUT> z non esiste
All'entrata nel sottoblocco viene generato un ambiente locale in cui vengono 'ricopiate' tutte le associazioni dell'ambiente globale, quindi vengono generate le associazioni di ambiente secondo quanto indicato nelle dichiarazioni.
Nell'esempio, si noti che la seconda definizione di $x
è locale al sottoblocco, e quindi maschera il valore globale. Ogni uso di $x
all'interno del sottoblocco pertanto farà riferimento all'ambiente locale. L'uso di $y
, invece, è riferito all'ambiente globale, e ben si vede nell'output generato. Si noti che alla chiusura del sottoblocco l'ambiente locale viene distrutto, pertanto le variabili dichiarate internamente non saranno più esistenti all'esterno del blocco.
Ambiente non locale
modificaInsieme di associazioni che devono essere ricavate da altri sottoprogrammi seguendo le regole di scoping messe a disposizione dal linguaggio.
Si supponga che un sottoprogramma P contenga un riferimento al nome x che non è locale per P e non è globale. Tale riferimento dovrà essere quindi risolto nell'ambiente di qualche altro sottoprogramma.
Vi saranno quindi due diverse possibilità:
- ambiente non locale 'dinamico' (scoping dinamico) - Il riferimento al nome x sarà risolto con l'ultima associazione per x presente nella catena di chiamate per P. Quindi in ogni attivazione di sottoprogramma o blocco viene generato un nuovo ambiente: in questo modo non è possibile stabilire a priori (staticamente) quale sarà l'associazione impiegata, in quanto sarà dipendente dal flusso di esecuzione del programma.
- ambiente non locale 'statico' (scoping statico o lessicale) - Il riferimento al nome x sarà risolto basandosi sulla struttura sintattica di nidificazione di blocchi e sottoprogrammi. Quindi l'ambiente non locale sarà quello di definizione del sottoprogramma (e quindi 'statico'), e non quello di applicazione (e quindi 'dinamico').
Per maggior chiarezza si veda l'esempio seguente, scritto in uno pseudolinguaggio:
01: Procedure B 02: var x:... // definita in B 03: Procedure P 04: <utilizzo x> 05: endProc P 06: beginBlock A 07: Procedure Q 08: var x:... // definita in Q, dentro il blocco A 09: Call P 10: endProc Q 11: Call Q 12: endBlock A 13: endProc B
- In un ambiente non locale dinamico, la chiamata di Q alla riga 11 provocherà l'attivazione di un nuovo ambiente locale, in cui x viene ridefinito (riga 8), e pertanto alla chiamata di P (riga 9), la x che P utilizzerà alla riga 4 sarà riferita a quest'ultima definizione. Si noti che in questo caso l'ambiente non locale può essere costruito solo a tempo di esecuzione.
- In un ambiente non locale statico, la x impiegata alla riga 4 è sempre e comunque quella definita nella riga 2, indipendentemente da altre ridefinizioni effettuate in altri sottoblocchi. Si noti che in questo modo si può definire l'ambiente non locale già a tempo di compilazione.
L'ambiente nei linguaggi interpretati
modificaIl concetto formale di ambiente spesso viene semplificato, nelle sue caratteristiche, quando si fa uso di un linguaggio interpretato. Ricadono in questa categoria, ad esempio, JavaScript, PHP, Perl, ma anche i più noti interpreti di comandi di un qualsiasi sistema operativo. Normalmente non è obbligatorio dichiarare anticipatamente, ad esempio, le variabili che verranno usate nel corpo del programma, in quanto l'interprete si fa carico di allocare la memoria e di creare le opportune associazioni nell'ambiente ogni qual volta una nuova variabile viene utilizzata.
- Nei linguaggi di scripting più evoluti l'ambiente può essere strutturato anche in maniera piuttosto complessa, come in un qualsiasi linguaggio imperativo che prevede la compilazione: in alcun casi, infatti, il programma viene sottoposto ad una compilazione on the fly ed eseguito in maniera più ottimizzata.
- Negli interpreti di comandi l'ambiente è spesso una semplice lista di associazioni tra un identificatore ed un valore di base (in genere, numero intero o stringa). Ogni nuova istanza dell'interprete, in genere, eredita l'ambiente dell'interprete chiamante (come nella shell di Unix o MS-DOS); analogamente ogni processo o comando invocato ha la possibilità di leggere tale ambiente, ed in alcuni casi di modificarlo. Spesso questo tipo di ambiente contiene informazioni legate alla gestione delle funzionalità proprie della shell stessa, come la lista delle directory dove cercare i comandi eseguibili (detta path), la directory di default dell'utente (detta home directory), le caratteristiche del terminale in uso, e così via.