Il 0% ha trovato utile questo documento (0 voti)
65 visualizzazioni174 pagine

Libro Semantica Operazionale

Il documento tratta della semantica operazionale dei linguaggi di programmazione, con un focus su quelli imperativi e funzionali. Viene presentato un approccio strutturale che facilita la comprensione e l'applicazione della semantica, supportato da esercizi pratici. L'obiettivo è fornire un testo didattico utile per studenti e professionisti nel campo dell'informatica, colmando una lacuna nella letteratura in lingua italiana.

Caricato da

silac18747
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
65 visualizzazioni174 pagine

Libro Semantica Operazionale

Il documento tratta della semantica operazionale dei linguaggi di programmazione, con un focus su quelli imperativi e funzionali. Viene presentato un approccio strutturale che facilita la comprensione e l'applicazione della semantica, supportato da esercizi pratici. L'obiettivo è fornire un testo didattico utile per studenti e professionisti nel campo dell'informatica, colmando una lacuna nella letteratura in lingua italiana.

Caricato da

silac18747
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd

I SABELLA M ASTROENI C ORRADO P RIAMI

S EMANTICA O PERAZIONALE :
S TRUMENTI E A PPLICAZIONI
Linguaggi Imperativi e Funzionali
Tecnologico

i
ii
Indice

1 Introduzione 1
1.1 Semantica operazionale . . . . . . . . . . . . . . . . . . . 1
1.2 Sistemi di transizione . . . . . . . . . . . . . . . . . . . . 3
1.3 Induzione . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4 Prospettive . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.5 Piano del lavoro . . . . . . . . . . . . . . . . . . . . . . . 6

2 Un semplice linguaggio imperativo 9


2.1 Ambienti e memorie . . . . . . . . . . . . . . . . . . . . 9
2.2 Sintassi . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3 Espressioni 17
3.1 Semantica di E XP . . . . . . . . . . . . . . . . . . . . . . 17
3.2 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4 Dichiarazioni 31
4.1 Semantica di D IC . . . . . . . . . . . . . . . . . . . . . . 32
4.2 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

5 Comandi 51
5.1 Semantica di C OM . . . . . . . . . . . . . . . . . . . . . 51
5.2 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

6 Procedure 97
6.1 Semantica delle procedure . . . . . . . . . . . . . . . . . 97
6.2 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

iii
7 Il paradigma funzionale 119
7.1 Il λ-calcolo . . . . . . . . . . . . . . . . . . . . . . . . . 120
7.2 Semantica statica . . . . . . . . . . . . . . . . . . . . . . 121
7.3 Semantica dinamica . . . . . . . . . . . . . . . . . . . . . 122
7.4 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

8 Un semplice linguaggio funzionale 135


8.1 Sintassi . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
8.2 Confronto con I MP . . . . . . . . . . . . . . . . . . . . . 138
8.3 Semantica di F UN . . . . . . . . . . . . . . . . . . . . . . 139
8.3.1 Semantica statica . . . . . . . . . . . . . . . . . . 141
8.3.2 Semantica dinamica . . . . . . . . . . . . . . . . 145
8.4 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

9 Conclusioni 161
9.1 Altri approcci . . . . . . . . . . . . . . . . . . . . . . . . 162
9.2 I linguaggi reali . . . . . . . . . . . . . . . . . . . . . . . 163
9.3 Approfondimenti . . . . . . . . . . . . . . . . . . . . . . 163

iv
Prefazione

La semantica dei linguaggi di programmazione è quella parte della teoria


che studia il significato dei programmi. Le tecniche operazionali che sono
affrontate nel testo poi cercano di formalizzare il significato di un pro-
gramma in termini delle azioni che una descrizione astratta di un generico
mezzo di calcolo deve compiere per eseguirli.
Abbiamo adottato nel testo la semantica operazionale strutturale poiché
è vicina all’intuizione e quindi può essere compresa senza approfondite co-
noscenze matematiche come invece accade per altre tecniche di definizione
semantica. Nonostante la semplicità dei concetti matematici su cui si basa,
l’approccio strutturale supporta tecniche formali di dimostrazione poiché
è strettamente legato alla logica.
Gli aspetti formali nello studio dei linguaggi di programmazione costi-
tuiscono un aspetto fondamentale della preparazione di un informatico che
voglia saper progettare o usare professionalmente un linguaggio. Lo stu-
dio teorico dei linguaggi caratterizza inoltre l’informatico rispetto a figure
affini come quella dell’ingegnere informatico. Inoltre, la necessità di inse-
gnare tecniche formali sin dai primi anni dei corsi di laurea in informatica
è ormai un requisito essenziale.
L’esigenza di scrivere un testo ricco di esercizi sulla semantica opera-
zionale strutturale dei linguaggi di programmazione imperativi e funziona-
li nasce dalla quasi totale assenza di materiale analogo in lingua italiana.
Anche i testi pubblicati in altre lingue prediligono la descrizione dei fon-
damenti della tecnica senza dedicare uno spazio adeguato alle applicazioni
di tali fondamenti ai costrutti dei linguaggi di programmazione. La pub-
blicazione alla quale maggiormente ci siamo ispirati è il rapporto tecnico
dell’Università di Aharus di Plotkin in cui viene introdotta la semantica

v
operazionale strutturale. Infatti parte degli esercizi che qui abbiamo risolto
sono proposti da Plotkin come lavoro per il lettore.
La parte sui linguaggi funzionali invece presenta prima un breve richia-
mo di λ-calcolo come fondamento del paradigma. Poi, seguendo ancora
parzialmente il lavoro di Plotkin, questo viene esteso fino ad ottenere un
semplice linguaggio funzionale. Abbiamo scelto questa strada per intro-
durre il paradigma funzionale poiché è quella adottata nella maggior parte
dei corsi tenuti nelle università europee.
Abbiamo preferito presentare sotto forma di esercizi anche alcuni aspet-
ti importanti dei linguaggi, come ad esempio certi tipi di passaggio dei
parametri, per stimolare il lettore a comprendere come la semantica opera-
zionale strutturale necessiti di pochissimi pre-requisiti per affrontare anche
problemi relativamente complessi. Inoltre questo dovrebbe evidenziare co-
me la semantica operazionale sia uno strumento che deve poi essere appli-
cato nella formalizzazione del significato dei costrutti dei linguaggi, ma
che non dipende da nessun linguaggio in particolare. In conclusione gli
esercizi possono anche essere interpretati come approfondimenti di quegli
aspetti minimali riguardanti la semantica dei linguaggi che sono presentati
nei vari capitoli prima delle sezioni dedicate agli esercizi.
Questo testo è adottato nel corso di Linguaggi: semantica, paradigmi
e macchine astratte tenuto al terzo anno del corso laurea in Informatica
dell’Università di Verona. Esso può essere usato in qualunque corso in cui
si vogliano presentare aspetti formali dei linguaggi di programmazione. La
sua struttura modulare basata su approfondimenti presentati come esercizi
lo rende versatile per una trattazione a diversi livelli di dettaglio.
Il materiale contenuto nel testo è stato presentato in due corsi sui lin-
guaggi di programmazione, e la sua forma finale è frutto di una mediazione
tra gli aspetti formali che devono essere trattati e le esigenze degli studenti
che devono apprenderli. Per questo motivo ringraziamo tutti gli studenti
dei corsi di Linguaggi III e Linguaggi IV dell’anno accademico ′ 98/′ 99 del
corso laurea in Informatica dell’Università di Verona per i loro commenti
e le loro critiche costruttive.
I SABELLA M ASTROENI
Verona, 2 marzo 2009
C ORRADO P RIAMI

vi
Capitolo 1

Introduzione

In questo testo richiamiamo le nozioni elementari di semantica operazio-


nale strutturale [18] dei linguaggi imperativi e funzionali. Poi presen-
tiamo sotto forma di esercizi approfondimenti ed estensioni dei concetti
richiamati.
Prima di addentrarci nello studio dei linguaggi imperativi e funziona-
li, presentiamo una breve introduzione alle principali caratteristiche della
semantica operazionale.

1.1 Semantica operazionale


Sin dalla nascita dell’informatica, il comportamento dinamico delle mac-
chine è stato definito attraverso approcci operazionali che descrivono le
transizioni tra gli stati che queste compiono durante il processo di calcolo.
La semantica operazionale fa la sua comparsa nella letteratura negli
anni ’60 grazie ai lavori di McCarthy [13] e Lucas [12]. In tale semantica
un programma viene considerato come una sequenza di istruzioni atomiche
che modificano gli stati delle macchine. Questi sono costituiti dai program-
mi stessi, dai dati su cui essi operano e da alcune strutture ausiliarie che
rappresentano la memoria della macchina. Il passaggio da uno stato (con-
figurazione) ad un altro è rappresentato mediante funzioni matematiche da
stati a stati, dette transizione della macchina. Se necessario, le transizioni
possono essere etichettate da informazioni che descrivono l’attività che ha

1
2 CAPITOLO 1. INTRODUZIONE

causato il passaggio di stato in modo da rendere più chiara l’evoluzione del


sistema. Possiamo quindi definire l’esecuzione di un programma (compu-
tazione) come una sequenza di stati connessi l’uno all’altro da transizioni.
PL/I e Algol60 sono stati i primi linguaggi di programmazione reali ad
avere una definizione mediante semantica operazionale.
La semantica operazionale è molto vicina all’intuizione poiché descri-
ve i passi che una macchina astratta compie durante il calcolo. Essa for-
nisce inoltre linee guida per i progettisti dei linguaggi senza però fissare
soluzioni implementative a priori. Tale semantica descrive una macchi-
na astratta facilmente simulabile da un interprete consentendo di costrui-
re rapidamente prototipi dei linguaggi che si vogliono definire per poterli
provare sul campo nel più breve tempo possibile.
La semantica operazionale di un linguaggio necessita di strutture da-
ti molto semplici e di alcune operazioni definite su esse per descrivere in
modo formale, e quindi non ambiguo, il significato dei programmi scritti
nel suddetto linguaggio. Quindi le tecniche operazionali sono utilizzabili
da una larga classe di utenti e sono anche adatte a scopi didattici. La tecni-
ca maggiormente utilizzata per dimostrare proprietà e per definire costrutti
mediante semantica operazionale è una forma di induzione detta strutturale
che generalizza l’induzione matematica. Perciò per definire il significato
di un programma in termini del significato dei suoi componenti elementa-
ri (composizionalità), dopo i lavori di de Bakker e de Roever [6], Plotkin
[18] propose un approccio strutturale (SOS). La sua novità sta nell’usare
come base la logica per la derivazione delle transizioni. Tali derivazio-
ni sono ottenute inducendo sulla struttura sintattica dei linguaggi espressa
in notazione BNF. Inoltre anche le transizioni sono definite induttivamen-
te mediante assiomi e regole di inferenza quindi è possibile costruire un
sistema di dimostrazione formale basato sulla logica.
L’approccio SOS è stato usato con successo per descrivere non solo
linguaggi imperativi e funzionali, ma anche logici, orientati agli oggetti,
concorrenti e distribuiti. Quindi la padronanza della tecnica consente di
poter affrontare lo studio di nuovi linguaggi senza dover acquisire nuo-
ve conoscenze matematiche di base. Inoltre la versatilità del metodo lo
rende adatto a descrivere linguaggi eterogenei che sempre più si rendono
necessari con il diffondersi delle reti di calcolatori.
1.2. SISTEMI DI TRANSIZIONE 3

Fino ad ora abbiamo assunto implicitamente di descrivere il comporta-


mento dinamico dei sistemi. Comunque la semantica operazionale strut-
turale è adatta anche a descrivere aspetti statici legati ai linguaggi. Gli
aspetti statici riguardano tutte quelle proprietà che possono essere dedotte
semplicemente dalla rappresentazione dei programmi e che non coinvol-
gono l’esecuzione (o la simulazione) dei costrutti che questi contengono.
Un buon esempio di utilizzo di questa tecnica è la descrizione dei sistemi
di tipi dei linguaggi per verificare staticamente se un programma applica
i suoi costrutti ad operandi dei tipi corretti [4]. Questi aspetti sono di so-
lito genericamente classificati come semantica statica dei linguaggi. Noi
presenteremo nei prossimi capitoli, oltre la semantica dinamica, anche la
semantica statica dei linguaggi imperativi e funzionali utilizzando questo
approccio.

1.2 Sistemi di transizione


Cerchiamo qui di raffinare il concetto di descrizione operazionale di un
sistema. Poiché il comportamento dinamico di un programma è definito
dalle transizioni tra stati, la struttura matematica più adeguata a modellarlo
è quella dei grafi. I grafi che definiamo mediante le regole di inferenza della
semantica operazionale di un linguaggio sono detti sistemi di transizione e
li trattiamo brevemente in questa sezione.
Un sistema è un modello matematico del fenomeno investigato ed è ca-
ratterizzato, ad un certo momento t, dal suo stato interno (configurazione)
costituito da un programma di controllo e da alcuni dati. Quella appena
fornita è una descrizione statica di sistema. Esso cambia il suo stato in-
terno in base agli stimoli che riceve da un ambiente in cui opera o in base
a particolari valori dei suoi dati. L’insieme delle configurazioni attraverso
cui può transire un sistema caratterizza il suo comportamento dinamico.
Per questo motivo parliamo dei sistemi di transizione come di modelli del
comportamento dinamico dei programmi.

Definizione 1.1 (sistema di transizione). Un sistema di transizione è una


struttura (Γ, →
− ), dove Γ è un insieme di elementi γ chiamati configurazioni
e la relazione binaria −
→ ⊆ Γ × Γ è chiamata relazione di transizione.
4 CAPITOLO 1. INTRODUZIONE

Nel seguito scriveremo γ − → γ ′ per hγ, γ ′i ∈ −


→. Alcune volte useremo la
→∗ di una relazione di transizione definita
chiusura riflessiva e transitiva −
da
→∗ γn ⇔ ∃γ1 . . . γn . γi −
γ0 − → γi+1 , i ∈ [0, n].
Individuando particolari sottoinsiemi di Γ che interpretiamo come in-
siemi degli stati finali T o degli stati iniziali I, otteniamo rispettivamente
i sistemi di transizione iniziali e terminali. Inoltre possiamo decidere di
etichettare le transizioni con elementi di un certo alfabeto per descrivere
l’azione del sistema che ne causa la transizione di stato. In questo caso
parliamo di sistemi di transizione etichettati.
Un sistema di transizione può essere rappresentato convenientemente
mediante un grafo in cui i nodi rappresentano le configurazioni e gli archi
orientati le transizioni. I possibili comportamenti di un sistema sono quindi
ottenuti visitando il corrispondente grafo del sistema di transizione. Per un
esempio di costruzione di un sistema di transizione si veda l’Esercizio 3.3.

1.3 Induzione
Richiamiamo brevemente una generalizzazione del principio di induzione
matematica detta induzione strutturale. Anziché prendere l’insieme N dei
numeri naturali come base, si considerano gli elementi di una categoria sin-
tattica e l’ordinamento sui naturali è sostituito dalla relazione componente
immediato di sugli elementi sintattici.
Consideriamo ad esempio la seguente grammatica che definisce i nu-
meri binari

b ::= 0 | 1 | b0 | b1

Gli elementi base sono costituiti dai letterali 0 e 1 che rappresentano i


numeri binari 0 e 1 rispettivamente. Gli elementi composti sono invece b0
e b1. Vale la relazione 0 è componente immediato di b0 e 1 è componente
immediato di b1. Se si vuole dimostrare una proprietà che vale per tutti i
numeri binari, si può dimostrarla per 0 e 1 come base. Se poi riusciamo a
dimostrarla anche per b0 e b1 assumendola valida per b, siamo in grado di
affermare che tutti i numeri binari verificano la proprietà.
1.3. INDUZIONE 5

Generalizzando l’applicazione dell’induzione strutturale vista per i nu-


meri binari ad un qualunque linguaggio definito mediante grammatiche o
notazione BNF otteniamo il seguente principio.

Principio 1.2 (induzione strutturale). Per dimostrare che una proprietà è


valida per tutti gli elementi di una categoria sintattica

1. si dimostra la proprietà per tutti gli elementi base della categoria


(quelli che non hanno nessun elemento come componente);

2. si dimostra la proprietà per tutti gli elementi composti assumendo


che la proprietà sia verificata da tutti i loro componenti immediati
(ipotesi induttiva).

La tecnica di dimostrazione per induzione strutturale è strettamente


correlata allo stile composizionale di definizione della semantica opera-
zionale strutturale. Infatti il significato dei costrutti dei linguaggi di pro-
grammazione è definito in termini del significato dei loro immediati com-
ponenti.
Un’altra tecnica di dimostrazione che si deriva direttamente dallo stile
logico di definizione dei sistemi di transizione è l’induzione sulla forma
degli alberi di derivazione delle transizioni.

Principio 1.3 (induzione sugli alberi di derivazione). Per dimostrare che


una proprietà vale per tutte le transizioni di un certo linguaggio

1. si dimostra che essa vale per gli alberi di derivazione semplici (co-
stituiti dagli assiomi della semantica operazionale strutturale per il
linguaggio considerato);

2. si dimostra che la proprietà vale per tutti gli alberi di derivazione


composti. Cioè si dimostra la proprietà per tutte le regole di infe-
renza (definite per le categorie sintattiche su cui si vuole dimostrare
la proprietà) della semantica operazionale supponendo, per ipotesi
induttiva, che essa sia vera per le premesse delle regole stesse.

Per un esempio di applicazione di questo principio vedi Esercizio 4.3.


6 CAPITOLO 1. INTRODUZIONE

1.4 Prospettive
Con l’evoluzione dell’informatica la complessità dei sistemi studiati e pro-
gettati cresce enormemente. I progettisti devono di volta in volta consi-
derare contemporaneamente una grande quantità e varietà di informazioni
per guidare le loro scelte. Per esempio l’esplosione del Web e di Internet
forniscono un ambiente globale di calcolo dove programmi allocati su siti
distinti, potenzialmente in movimento (si pensi ai computer di bordo di un
aereo), possono cooperare o persino migrare durante il calcolo. Questo im-
pone l’integrazione di diverse applicazioni residenti in diversi siti e, poten-
zialmente, scritte in linguaggi distinti basati su paradigmi distinti. Quindi
un requisito indispensabile è che le tecniche di specifica e di verifica siano
quanto più possibile indipendenti dai linguaggi e dalle architetture.
L’attenzione principale delle tecniche SOS è rivolta al modo in cui vie-
ne definito il significato di un costrutto di programmazione piuttosto che
ad uno specifico linguaggio. Vedremo infatti come sia possibile definire
il significato di costrutti di linguaggi diversi senza dover cambiare l’im-
pianto formale che utilizziamo. Quindi la specifica di un linguaggio può
essere modificata durante il progetto o sintonizzata alle particolari esigen-
ze del progettista con pochissimo sforzo e con un ridotto spreco di risorse.
In conclusione la tecnica SOS rappresenta uno stile di definizione della
semantica dei linguaggi.
Altro aspetto interessante dell’approccio utilizzato in questo testo è la
possibilità di vedere le definizioni SOS come il nucleo di un generatore di
interpreti. Dato un particolare dominio applicativo il progettista individua
i costrutti essenziali per descrivere le soluzioni dei problemi e li defini-
sce utilizzando la semantica SOS. Il generatore automatico di interpreti (o
compilatori) produce un linguaggio orientato al dominio con un piccolo (e
quindi efficiente) supporto a tempo di esecuzione.

1.5 Piano del lavoro


Nel prossimo capitolo richiamiano i concetti di base dei linguaggi impera-
tivi come ambienti e memorie. Inoltre riportiamo la sintassi di un semplice
linguaggio che useremo come base per gli esercizi proposti.
1.5. PIANO DEL LAVORO 7

Il Capitolo 3 dopo aver introdotto la semantica statica e dinamica delle


espressioni affronta la soluzione di alcuni problemi. La stessa struttura è
seguita nei Capitoli 4, 5, 6 per le dichiarazioni, i comandi e le procedure
rispettivamente.
Il Capitolo 7 richiama le nozioni fondamentali del λ-calcolo visto co-
me base del paradigma di programmazione funzionale. Quindi affronta la
soluzione di alcuni esercizi.
Il Capitolo 8 arricchisce la sintassi del λ-calcolo ottenendo un linguag-
gio funzionale ML-like. Dopo aver definito la semantica statica e dinamica
del nuovo linguaggio, forniamo la soluzione di alcuni esercizi proposti.
Il Capitolo 9 fornisce alcune considerazioni conclusive e inquadra al-
cuni aspetti studiati nel panorama dei vari linguaggi di programmazione.
8 CAPITOLO 1. INTRODUZIONE
Capitolo 2

Un semplice linguaggio
imperativo

In questo capitolo richiamiamo brevemente le caratteristiche essenziali dei


linguaggi imperativi e forniamo la sintassi del linguaggio che assumiamo
definito per la soluzione degli esercizi presentati nei prossimi capitoli. La
semantica statica e dinamica di questo nucleo che chiameremo I MP è de-
finita suddividendo i costrutti in base alle categorie sintattiche individuate
(espressioni, dichiarazioni e comandi) ed è riportata nei corrispondenti ca-
pitoli. Per motivi didattici le procedure sono trattate in un capitolo a parte
anche se la loro sintassi rientra in parte nella categoria sintattica delle di-
chiarazioni e in parte in quella dei comandi. Infine richiamiamo i concetti
di induzione strutturale e di regole di inferenza.
Nella prossima sezione richiamiamo la definizione delle strutture che uti-
lizzeremo nelle definizioni semantiche: ambienti statici e dinamici e me-
morie ed in quella successiva introduciamo la sintassi di I MP. Nell’ultima
sezione richiamiamo brevemente alcuni principi di induzione utilizzati nel
testo.

2.1 Ambienti e memorie


Per tener traccia dei valori assegnati agli identificatori nei linguaggi impe-
rativi facciamo ricorso al concetto di memoria. Una memoria è una col-

9
10 CAPITOLO 2. UN SEMPLICE LINGUAGGIO IMPERATIVO

lezione di locazioni che possono essere aggiornate dinamicamente. Una


locazione può essere in tre stati distinti: inutilizzata, indefinita o definita.
La locazione è inutilizzata quando non è ancora stata allocata per qualche
identificatore. Si trova nello stato indefinito se è già stata allocata, ma non
contiene ancora un valore (il corrispondente identificatore è stato dichiara-
to, ma non ancora inizializzato). Infine, la locazione è definita quando con-
tiene un valore. Nel seguito utilizzeremo il termine memoria per denotare
un’immagine dell’insieme di locazioni ad un istante particolare.
Poiché il valore degli identificatori, cosı̀ come quello delle locazioni,
può variare dinamicamente è ragionevole associare identificatori a loca-
zioni. Il valore di un identificatore sarà poi quello contenuto nella corri-
spondente locazione. Le locazioni sono un meccanismo di indirizzamen-
to indiretto tra gli identificatori e i valori che possono memorizzare. Per
formalizzare questa associazione definiamo prima le locazioni.
Definizione 2.1 (locazione). Per ogni valore memorizzabile di tipo τ , sia
Locτ un insieme infinito di celle di memoria che possono memorizzare
valori di tipo τ . Quindi la collezione di locazioni è

Loc = ∪τ ∈ STyp Locτ

con metavariabile l, dove STyp è l’insieme dei tipi composti da elementi


memorizzabili (vedi prossima sezione).
A questo punto siamo in grado di definire il concetto di memoria.
Definizione 2.2 (memoria). Una memoria è un elemento dello spazio di
funzioni

Stores = ∪L⊆f Loc StoreL

dove ⊆f significa “un sottoinsieme finito di” e StoreL : L → SV al ∪


{?, ⊥} ha metavariabile σ. I simboli ? e ⊥ denotano rispettivamente lo
stato inutilizzato e indefinito. Imponiamo alle memorie la condizione

∀l ∈ L . σ(l) ∈ τ ⇔ l ∈ L ∩ Locτ

per assicurare che le locazioni siano utilizzate correttamente dal punto di


vista dei tipi.
2.1. AMBIENTI E MEMORIE 11

L’operazione di aggiornamento delle memorie è definita come segue. Sia-


no L, L′ ⊆f Loc, σ ∈ StoreL e σ ′ ∈ StoreL′ . Definiamo σ[σ ′ ] ∈ StoreL∪L′
come
 ′
′ σ (l) l ∈ L′
σ[σ ](l) =
σ(l) l ∈ (L − L′ )

Se L∩L′ = ∅ scriviamo σ, σ ′ al posto di σ[σ ′ ]. La notazione σ : L significa


che il dominio di σ è L.

A questo punto possiamo introdurre il concetto di ambiente (dinamico)


Env che associa gli identificatori con le locazioni della memoria. Quindi,
componendo ambienti e memorie recuperiamo il valore di un identificato-
re. Più precisamente, un ambiente lega gli identificatori con i valori deno-
tabili DVal. Lo speciale valore indefinito (ancora scritto ⊥) è necessario
per modellare il fatto che un identificatore non è legato a nessun valore
denotabile nell’ambiente corrente.

Definizione 2.3 (ambiente). Un ambiente dinamico è un elemento dello


spazio di funzioni

Env = ∪I⊆f Id EnvI

dove EnvI : I → DV al ∪ Loc ∪ {⊥} ha metavariabile ρ. Le operazioni


di aggiornamento degli ambienti ρ[ρ′ ] ∈ EnvI∪I ′ e ρ, ρ′ ∈ EnvI∪I ′ so-
no definite come per le memorie. Anche la notazione ρ : I ha lo stesso
significato.

Infine, come vedremo nei prossimi capitoli, questi ambienti vengono


generati e modificati dalle dichiarazioni che a nuovi identificatori associa-
no un valore (nel caso di dichiarazioni di costante) o una locazione (nel
caso di dichiarazioni di variabili). Per quest’ultimo caso dobbiamo de-
finire una funzione che ad ogni nuovo identificatore definito associ una
locazione non ancora utilizzata. Per fare ciò dobbiamo supporre di po-
ter ordinare le locazioni dello stesso tipo in modo da poter associare lo-
ro un valore naturale nel momento in cui vengono utilizzate, in tal modo
possiamo distinguerle da quelle non ancora usate.
12 CAPITOLO 2. UN SEMPLICE LINGUAGGIO IMPERATIVO

Definizione 2.4 (generatore di locazioni). Un generatore di locazioni è una


funzione New : Loc × DT yp → Loc × N definita come la funzione che
esegue la seguente associazione:

New(L, τ ) = hl, mi, L ⊆f Locτ , l ∈ Locτ ,


m = (max{n | ∃hl, ni . l ∈ L}) + 1

In generale, della coppia risultante, considereremo solo la locazione


(vista la sola utilità tecnica della numerazione) usando la notazione l ∈
Newτ (L) al posto di quella formalmente corretta hl, mi ∈ New(L, τ ).
A questo punto, in modo analogo agli ambienti introdotti sopra per
associare dinamicamente identificatori e locazioni, possiamo definire degli
ambienti statici ∆ che associano agli identificatori il tipo dei valori che poi
denoteranno a tempo di esecuzione. Questi ambienti saranno la base per la
definizione della semantica statica.

Definizione 2.5 (ambiente statico). Un ambiente statico (o di tipi) è un


elemento dello spazio di funzioni TEnv definito da

T Env = ∪I⊆f Id T EnvI

dove T EnvI : I → DTyp ha metavariabile ∆ e DTyp è l’insieme dei


tipi denotabili. Le operazioni di aggiornamento degli ambienti ∆[∆′ ] ∈
T EnvI∪I ′ e ∆, ∆′ ∈ T EnvI∪I ′ sono definite come per le memorie. Anche
la notazione ∆ : I ha lo stesso significato.

Per essere precisi, scriveremo ∆ ⊢I per rendere esplicito l’insieme


finito di identificatori I che costituisce il dominio di ∆.
La seguente definizione stabilisce la compatibilità tra gli ambienti di-
namici ρ e gli ambienti statici ∆.

Definizione 2.6 (compatibilità di ambienti). Sia ρ : I un ambiente dina-


mico e ∆ : I un ambiente statico con I ⊆f Id. Gli ambienti ρ e ∆ sono
compatibili (scritto ρ : ∆) se e soltanto se

∀id ∈ I . (∆(id) = τ ∧ ρ(id) ∈ τ ) ∨


∃τ . (∆(id) = τ loc ∧ ρ(id) ∈ Locτ )
2.2. SINTASSI 13

Nelle regole di inferenza contenute negli esercizi proposti di seguito


usiamo sempre come ambienti ∆ e ρ invece degli ambienti vuoti perché
supponiamo che le regole fornite costistuiscano un frammento di codice
e che quindi siano inserite in un contesto più ampio. Infatti, se si deve
esaminare un intero programma gli ambienti (statico e dinamico) con cui
iniziamo l’applicazione delle regole sono quelli vuoti.

2.2 Sintassi
Qui richiamiamo brevemente la sintassi del linguaggio di programmazione
I MP utilizzando la notazione BNF. Gli insiemi sintattici di base sono
T IPI. Typ = {bool, int} con metavariabile τ .
VALORI DI VERIT À . bool = {tt, ff } con metavariabile t.
N UMERI. int = {. . . , −1, 0, 1, . . .} con metavariabili m, n.
I DENTIFICATORI. Id = {rate, a25, b, x, . . .} con metavariabile id,
x.
O PERATORI UNARI . Uop = {not} con metavariabile uop.
O PERATORI BINARI . Bop = {+, −, ×, =, or} con metavariabile
bop.
e le categorie sintattiche derivate di I MP sono
C OSTANTI. Con con metavariabile k definita da
k ::= n | t

E SPRESSIONI. Exp con metavariabile e definite come


e ::= k | id | e bop e′ | uop e.

D ICHIARAZIONI. Dic con metavariabile d definite come

d ::= nil | const x : τ = e | var x : τ = e | d; d | d and d |


d in d | f orm = ae | procedure p(f orm) c | ρ
14 CAPITOLO 2. UN SEMPLICE LINGUAGGIO IMPERATIVO

C OMANDI. Com con metavariabile c definiti come

c ::= nil | id := e | c; c | if e then c else c | while e do c |


d; c | p(ae)

ATTUALI. AExp con metavariabile ae definiti come

ae ::= • | e, ae

ATTUALI VALUTATI . ACon con metavariabile ak definiti come

ak ::= • | k, ak

F ORMALI. Form con metavariabile form definiti come

form ::= • | const x : τ, form | var x : τ, form

C HIUSURE . Abs con metavariabile abs definite come

abs ::= λ f orm.c

VALORI ESPRIMIBILI. Eval = bool ∪ int con metavariabile ev defi-


niti come

ev ::= k

T IPI ESPRIMIBILI . ETyp = Typ con metavariabile et definito come

et ::= τ

VALORI DENOTABILI. DVal = bool ∪ int ∪ Loc ∪ Abs con metava-


riabile dv definiti come

dv ::= k | l | abs
2.2. SINTASSI 15

T IPI DENOTABILI. DTyp = Typ ∪ (Typ × loc) ∪ (ATyp × proc) con


metavariabile dt definiti come

dt ::= τ | τ loc | aetproc

VALORI MEMORIZZABILI. SVal = bool ∪ int con metavariabile sv


definiti come

sv ::= k

T IPI MEMORIZZABILI. STyp = bool ∪ int con metavariabile st


definiti come

st ::= τ

T IPI DEGLI ATTUALI . ATyp con metavariabile aet definiti come

aet ::= • | et, aet

Alcuni commenti sono necessari per le categorie sintattiche derivate. Le


costanti costituiscono i valori a cui sono valutate le espressioni che in I MP
possono solo essere interi (n) o booleani (t).
Le espressioni sono costituite componendo costanti o identificatori at-
traverso gli operatori unari e binari.
Le dichiarazioni contengono la dichiarazione di costante (const) e di
identificatore modificabile (var) che possono essere composte nelle di-
chiarazioni sequenziali (;), simultanee (and) o private (in). Inoltre è pos-
sibile dichiarare identificatori come nomi di procedure. La dichiarazione
f orm = ae serve per implementare il passaggio dei parametri operando
il legame tra i formali introdotti nella dichiarazione di una procedura e gli
attuali presenti nella chiamata della procedura. Infine, l’ambiente ρ è intro-
dotto nella sintassi delle dichiarazioni per motivi tecnici che saranno chiari
quando definiremo la semantica dinamica delle dichiarazioni.
I comandi, oltre all’assegnamento, la composizione sequenziale, il co-
mando condizionale e il comando iterativo, contengono il costrutto di bloc-
co d; c. Questo serve a delimitare il campo di azione delle dichiarazioni
16 CAPITOLO 2. UN SEMPLICE LINGUAGGIO IMPERATIVO

ed è strettamente legato al concetto di scoping che tratteremo in dettaglio


nel capitolo sulle procedure. Infine abbiamo la chiamata di procedura che
individua i parametri attuali ae.
I parametri attuali sono una lista di espressioni che quindi viene valu-
tata ad una lista di costanti (vedi la categoria sintattica degli attuali valuta-
ti). Abbiamo introdotto anche gli attuali valutati poiché I MP implementa
il passaggio dei parametri per valore e quindi gli attuali vengono valutati
completamente prima di legarli ai formali.
I parametri formali sono dichiarati in modo analogo alla dichiarazione
degli identificatori con la sola differenza che non sono inizializzati. La
loro inizializzazione avverrà al momento della chiamata della procedura
che contiene i corrispondenti attuali.
La categoria sintattica delle chiusure è stata introdotta per avere una
struttura da legare nell’ambiente dinamico agli identificatori di procedura.
Infatti la chiusura contiene tutta l’informazione che ci occorre: i parametri
formali ed il codice del corpo della procedura.
Abbiamo poi tre coppie di categorie sintattiche simili che definiscono
i valori esprimibili, denotabili e memorizzabili ed i loro corrispondenti
tipi. I valori esprimibili sono quelli che possono essere restituiti come
valutazione di un’espressione ed abbiamo già detto essere le costanti.
I valori denotabili sono quelli che possono essere legati agli identifica-
tori negli ambienti dinamici. Oltre i valori esprimibili abbiamo quindi le
locazioni e le chiusure. Il tipo corrispondente alle locazioni è τ loc dove
τ indica il tipo memorizzabile nella locazione ed il suffisso loc esplicita
il fatto che si tratta di una locazione. Il tipo di una chiusura corrisponde
alla lista di tipi associata ai formali (che corrisponde poi alla lista dei tipi
associata agli attuali) seguita dal suffisso proc.
Infine i valori memorizzabili sono quelli che possono essere registrati
in una locazione di memoria che nel caso di I MP coincidono con quelli
esprimibili.
L’ultima categoria sintattica che abbiamo descrive i tipi dei parametri
attuali. Questi sono una lista di tipi esprimibili in quanto possiamo passa-
re solo espressioni come attuali che poi sono interamente valutate poiché
implementiamo il passaggio dei parametri per valore.
Capitolo 3

Espressioni

Le espressioni sono una categoria sintattica che compare in tutti i linguaggi


di programmazione. Esse sono valutate per ottenere un valore. Il tipo di un
valore ottenuto dalla valutazione delle espressioni è uno dei tipi esprimi-
bili (ETyp) del linguaggio. L’insieme di tipi ETyp definisce naturalmente
l’insieme dei valori esprimibili EVal.
I costituenti elementari delle espressioni sono i letterali (nel nostro lin-
guaggio I MP costanti ed identificatori) che sono poi composti mediante gli
operatori.
In questo capitolo definiremo le espressioni aritmetiche e booleane for-
nendo la loro semantica statica e dinamica nella prima sezione. Quindi
presenteremo alcuni esercizi risolti nella sezione successiva.

3.1 Semantica di E XP
Introduciamo per prima cosa una funzione che individua l’insieme de-
gli identificatori che hanno occorrenze libere all’interno di un’espressio-
ne. Nel seguito ometteremo il termine occorrenze riferendoci a loro come
identificatori. Il contesto consente di eliminare l’ambiguità tra il riferimen-
to alle occorrenze e quello agli identificatori.

Definizione 3.1 (identificatori liberi). La funzione F V : Exp → Id, che


ad ogni espressione associa l’insieme degli identificatori liberi in essa

17
18 CAPITOLO 3. ESPRESSIONI

contenuti, è definita per induzione da

F I(k) = ∅
F I(id) = {id}
F I(e0 bop e1 ) = F I(e0 ) ∪ F I(e1 )
F I(uop e) = F I(e)

Per le epressioni la funzione DI : Exp → Id che individua gli iden-


tificatori in posizione di definizione è la costante ∅ in quanto nel nostro
linguaggio I MP le espressioni non definiscono identificatori.
Definiamo adesso la semantica statica per le espressioni del nostro lin-
guaggio. Lo scopo della semantica statica è quello di associare un tipo a
ciascuna espressione corretta; se questo accade diremo che l’espressione è
ben formata. Le regole sono riportate in Tab. 3.1.

∅ ⊢∅ t : bool ∅ ⊢∅ n : int ∆ ⊢I id : τ, ∆(id) ∈ {τ, τ loc}

∆ ⊢I e : τ0 , e′ : τ1 ∆ ⊢I e : τ
∆ ⊢I e bop e′ : τbop (τ0 , τ1 ) ∆ ⊢I uop e : τuop
Tabella 3.1: Semantica statica per le espressioni di I MP.

I tre assiomi assegnano un tipo agli elementi base su cui sono costrui-
te le espressioni. I primi due hanno ambiente dei tipi vuoto poiché non
occorre alcuna informazione per associare il tipo τ agli elementi stessi.
L’assioma per gli identificatori associa alle occorrenze di id il tipo regi-
strato in ∆ (vedremo che saranno le dichiarazioni a costruire gli ambienti
di tipo) purché non sia il tipo di una procedura. Le due regole servono per
assegnare il tipo alle espresioni composte. La prima considera gli operatori
binari bop = {=, +, −, ∗, or}. La funzione τbop : ET yp × ET yp →
ET yp ∪ {⊥}, definita in Tab. 3.2 (abbiamo usato il simbolo ⊥ per i casi in
cui la funzione non è definita), determina il tipo dell’espressione composta
a partire dal tipo delle espressioni componenti, disponibile nelle premesse
3.1. SEMANTICA DI EXP 19

+, −, ∗ int bool = int bool


int int ⊥ int bool ⊥
bool ⊥ ⊥ bool ⊥ bool

or int bool
not int bool
int ⊥ ⊥
⊥ bool
bool ⊥ bool
Tabella 3.2: Definizione delle funzioni τbop e τuop

della regola. L’ultima regola è analoga alle precedenti e gestisce gli opera-
tori unari. La funzione τuop : ET yp → ET yp ∪ {⊥} è definita in Tab.
3.2.
Le regole della semantica dinamica descrivono come vengono valutate
le espressioni di I MP considerando che le configurazioni terminali per le
espressioni sono del tipo hk, σi. Le regole sono riportate in Tab. 3.3. Il
primo assioma descrive la valutazione degli identificatori. La semantica
associa a ciascun id il valore registrato in ρ se id è una costante oppure il
valore contenuto nella locazione di memoria l associata a id in ρ se id è un
identificatore modificabile. Notiamo che non possiamo avere identificato-
ri di procedure poiché la semantica statica non riuscirebbe ad assegnare
un tipo all’espressione. Le successive due regole permettono la valutazio-
ne delle epressioni coinvolte in operazioni mediante un operatore binario
bop; si nota che la valutazione va da sinistra a destra, cioè si valuta l’e-
spressione a destra dell’operatore solo dopo aver finito la valutazione del-
l’espressione a sinistra. La terza regola ci dice che i passi di valutazione
di una espressione possono essere fatti anche nel contesto di un operatore
unario. Infine, i due assiomi permettono di completare l’operazione as-
sociando all’operatore sintattico applicato ai suoi operandi il valore risul-
tato del corrispondente operatore semantico applicato all’interpretazione
semantica degli operandi.
Per caratterizzare il comportamento dinamico della valutazione di un’e-
spressione, definiamo una funzione che restituisce il valore associato all’e-
20 CAPITOLO 3. ESPRESSIONI

ρ ⊢∆ hid, σi −
→e hk, σi, k = ρ(id) ∨ (ρ(id) = l ∧ k = σ(l))

ρ ⊢∆ he, σi −
→e he0 , σi
ρ ⊢∆ he bop e′ , σi −
→e he0 bop e′ , σi

ρ ⊢∆ he′ , σi −
→e he1 , σi
ρ ⊢∆ hk bop e′ , σi −
→e hk bop e1 , σi

→e he′ , σi
ρ ⊢∆ he, σi −
→e huop e′ , σi
ρ ⊢∆ huop e, σi −

ρ ⊢∆ hk bop k ′ , σi −
→e hk ′′ , σi, k ′′ = k bop k ′

→e ht′ , σi, t′ = uop t


ρ ⊢∆ huop t, σi −
Tabella 3.3: Semantica dinamica per le espressioni di I MP.

spressione nelle configurazioni finali.


Definizione 3.2 (valutazione). La funzione
Eval : Exp × Store → Con,
che descrive il comportamento dinamico delle espressioni a partire da una
memoria σ restituendo il valore in cui esse sono valutate, è definita da
Eval(e, σ) = k ⇔ he, σi →∗e hk, σi
A questo punto possiamo utilizzare il comportamento dinamico delle
espressioni per definire una relazione di equivalenza sugli elementi di Exp

Definizione 3.3 (equivalenza). L’equivalenza di espressioni ≡ ⊆ Exp ×


Exp è definita da
e0 ≡ e1 ⇔ ∀σ. (Eval(e0 , σ) = Eval(e1 , σ))
3.2. ESERCIZI 21

3.2 Esercizi
Esercizio 3.1. Si scrivano le regole per la valutazione delle espressioni da
destra a sinistra, contrapposta alla valutazione da sinistra a destra definita
nella semantica di I MP.
S OLUZIONE . Nella valutazione da destra a sinistra un’espressione transi-
sce quando si effettua una transizione nel suo sottotermine più a destra e
solo quando a destra si ha una costante si procede col ridurre il termine a
sinistra. Le regole della semantica dinamica sono

ρ ⊢∆ he, σi →e he′ , σi
1.
ρ ⊢∆ he0 bop e, σi →e he0 bop e′ , σi

ρ ⊢∆ he0 , σi →e he′0 , σi
2.
ρ ⊢∆ he0 bop k, σi →e he′0 bop k, σi
3. ρ ⊢∆ hk0 bop k, σi →e hk ′ , σi dove k ′ = k0 bop k
Le regole per gli operatori unari e gli identificatori non devono essere mo-
dificate poiché hanno una sola espressione come componente. 2

Esercizio 3.2. Definire le regole per la valutazione parallela delle espres-


sioni, in modo che la seguente sequenza di transizioni sia possibile:

(1 + (2 + 3)) + ((4 + 5) + 6) →e (1 + (2 + 3)) + (9 + 6) →e


(1 + 5)(9 + 6) →e 6 + (9 + 6) →e 6 + 15 →e 21

Mostrare, quindi, che la sequenza di transizioni può essere derivata dalle


regole fornite.
S OLUZIONE . Per risolvere l’esercizio bisogna notare che per permettere
il parallelismo delle esecuzioni delle transizioni bisogna avere regole che
permettano sia la valutazione da sinistra a destra che quella da destra a
sinistra (come suggerisce la sequenza fornita dall’esercizio). Le regole
della semantica dinamica sono quindi

ρ ⊢∆ he0 , σi →e he′0 , σi
1.
ρ ⊢∆ he0 bop e1 , σi →e he′0 bop e1 , σi
22 CAPITOLO 3. ESPRESSIONI

ρ ⊢∆ he1 , σi →e he′1 , σi
2.
ρ ⊢∆ he0 bop e1 , σi →e he0 bop e′1 , σi
3. ρ ⊢∆ hk0 bop k1 , σi →e hk ′ , σi dove k ′ = k0 bop k1
Nel seguito etichetteremo le transizioni con dei numeri per identificarle
univocamente all’interno di computazioni (sequenze di transizioni). Inol-
tre annoteremo gli alberi di derivazione delle transizioni con i nomi delle
1
regole applicate. Per esempio nella derivazione della transizione −→ appli-
chiamo prima l’assioma (3.), poi la regola (1.) ed infine la regola (2.). Si
noti che il nome della regola è riportato a destra della conclusione dell’i-
stanza dell’applicazione della regola.

A questo punto vediamo mediante quale sequenza di regole sopra definite


si riesce a derivare la computazione data:
1 2
(1 + (2 + 3)) + ((4 + 5) + 6) −
→ (1 + (2 + 3)) + (9 + 6) −

3 4 5
(1 + 5) + (9 + 6) −
→ 6 + (9 + 6) −
→ 6 + 15 −
→ 21

ρ ⊢∆ h(4 + 5), σi →e h9, σi (3.)


1 ρ ⊢∆ h(4 + 5) + 6, σi →e h9 + 6, σi (1.)
→:
ρ ⊢∆ h(1 + (2 + 3)) + ((4 + 5) + 6), σi →e
h(1 + (2 + 3)) + (9 + 6), σi (2.)

ρ ⊢∆ h(2 + 3), σi →e h5, σi (3.)


2 ρ ⊢∆ h1 + (2 + 3)σi →e h1 + 5, σi (2.)
→:
ρ ⊢∆ h(1 + (2 + 3)) + (9 + 6), σi →e h(1 + 5) + (9 + 6), σi (1.)

3
ρ ⊢∆ h(1 + 5), σi →e h6, σi (3.)
→:
ρ ⊢∆ h(1 + 5) + (9 + 6), σi →e h6 + (9 + 6), σi (1.)

4
ρ ⊢∆ h(9 + 6), σi →e h15, σi (3.)
→:
ρ ⊢∆ h6 + (9 + 6), σi →e h6 + 15, σi (2.)
3.2. ESERCIZI 23

(1 × 3) + (4 − (2 × 2))

3 + (4 − (2 × 2)) (1 × 3) + (4 − 4)

3 + (4 − 4) (1 × 3) + 0

3+0

Figura 3.1: Sistema di transizione di (1 × 3) + (4 − (2 × 2)).

5
→: ρ ⊢∆ h(6 + 15), σi →e h21, σi (3.)

Si noti che, poiché non ci sono identificatori nelle espressioni, possiamo


assumere sia ρ che σ vuoti. 2

Esercizio 3.3. Assumendo le regole introdotte nell’Esercizio 3.2 per la va-


lutazione parallela delle espressioni, costruire il sistema di transizione di
(1 × 3) + (4 − (2 × 2)).

S OLUZIONE . Non riportiamo ambiente e memoria nella costruzione del


sistema di transizione poiché l’espressione è costituita solo da costanti.
Lo stato iniziale è quindi l’espressione stessa da cui sono possibili due
transizioni a seconda che si valuti prima la moltiplicazione di sinistra o di
destra.
24 CAPITOLO 3. ESPRESSIONI

Le derivazioni corrispondenti alle prime due transizioni sono

ρ ⊢∆ h(1 × 3), σi →e h3, σi (3.)


ρ ⊢∆ h(1 × 3) + (4 − (2 × 2)), σi →e h3 + (4 − (2 × 2)), σi (1.)

per la valutazione da sinistra, e

ρ ⊢∆ h(2 × 2), σi →e h4, σi (3.)


ρ ⊢∆ h(4 − (2 × 2)), σi →e h4 − 4, σi (2.)
ρ ⊢∆ h(1 × 3) + (4 − (2 × 2)), σi →e h(1 × 3) + (4 − 4), σi (2.)

per la valutazione da destra. Proseguendo con la costruzione di tutte le pos-


sibili derivazioni fino alla configurazione finale h3, σi otteniamo il sistema
di transizione in Fig. 3.1. 2

Esercizio 3.4. Si modifichi la sintassi di I MP aggiungendo alla categoria


sintattica delle espressioni il costrutto:

e ::= (id := e)

dove l’espressione viene valutata al valore di e contemporaneamente al-


l’esecuzione dell’assegnamento che, in generale, causa side-effects.
S OLUZIONE . È importante capire cosa deve essere modificato nel linguag-
gio base da cui partiamo (I MP) in relazione ai costrutti aggiunti. Si nota in-
fatti che tale costrutto introduce la possibilità che si verifichino side-effects
nella valutazione delle espressioni poiché le nuove espressioni modificano
la memoria. Esse però non alterano l’insieme di identificatori in posizione
di definizione in quanto comunque non contengono dichiarazioni. Allo-
ra dobbiamo solo aggiungere la seguente definizione per gli identificatori
liberi:

F I(id := e) = {id} ∪ F I(e)

Definiamo ora la semantica statica e dinamica del nuovo costrutto.


3.2. ESERCIZI 25

Semantica statica
Consideriamo l’ambiente di tipi iniziale ∆ sull’insieme I di identificatori,
in base al quale determiniamo il tipo della nuova espressione
∆ ⊢I e : τ
, ∆(id) = τ loc
∆ ⊢I id := e : τ
Quindi decidiamo di assegnare all’espressione id := e il tipo che la se-
mantica statica assegna ad e.

Semantica dinamica
Valutiamo prima l’espressione e registrando in σ le eventuali modifiche
ρ ⊢∆ he, σi →e he′ , σ ′ i
ρ ⊢∆ hid := e, σi →e hid := e′ , σ ′ i
e poi assegnamo il valore k ottenuto alla locazione di id restituendolo
contemporaneamente come risultato della valutazione dell’espressione.
ρ ⊢∆ he, σi →e hk, σ ′ i
, l = ρ(id)
ρ ⊢∆ hid := k, σi →e hk, σ[k/l]i
Inoltre poiché la valutazione delle espressioni può modificare la memo-
ria, dobbiamo rimpiazzare ciascuna transizione ρ ⊢∆ he, σi →e he′ , σi in
Tab. 3.3 con ρ ⊢∆ he, σi →e he′ , σ ′ i. 2

Esercizio 3.5. Definire semantica statica e dinamica della seguente nuova


espressione
e ::= if e0 then e1 else e2
con la semantica intuitiva di valere e1 se e0 è vera ed e2 altrimenti.
S OLUZIONE . Come nei precedenti esercizi, la prima cosa da fare è definire
gli insiemi degli identificatori liberi (non ci sono identificatori in posizione
di definizione perché le espressioni non contengono dichiarazioni)
F I(if e0 then e1 else e2 ) = F I(e0 ) ∪ F I(e1 ) ∪ F I(c2 )
26 CAPITOLO 3. ESPRESSIONI

Semantica statica
Consideriamo l’ambiente di tipi iniziale ∆ definito sull’insieme I di iden-
tificatori, in base al quale definiamo una semantica statica. Si nota che,
essendoci una condizione, l’espressione è ben formata solo nel caso in cui
e0 sia booleano. Le altre espressioni possono essere di qualunque tipo
purché lo stesso per entrambe in quanto l’uso dell’una o dell’altra in ogni
contesto deve essere indifferente.
∆ ⊢I e0 : bool, ∆ ⊢I e1 : τ0 , ∆ ⊢I e2 : τ0
∆ ⊢I if e0 then e1 else e2 : τ0

Se permettessimo ad e1 di essere di tipo τ1 e a e2 di essere di tipo τ2 non


potremmo determinare staticamente la correttezza del frammento

4 = if e0 then tt else 3

Infatti l’espressione è corretta solo se e0 è falsa.


Semantica dinamica
La valutazione di tale espressione deve coincidere con la valutazione di e1
se e0 vale tt, mentre deve coincidere con la valutazione di e2 altrimenti. La
prima regola sotto esprime il fatto che la valutazione della guardia e può
avvenire anche nel contesto if e che questa può richiedere più passi.

ρ ⊢∆ he0 , σi →e he′0 , σi
ρ ⊢∆ hif e0 then e1 else e2 , σi →e hif e′0 then e1 else e2 , σi

ρ ⊢∆ he0 , σi →e htt, σi
ρ ⊢∆ hif e0 then e1 else e2 , σi →e he1 , σi

ρ ⊢∆ he0 , σi →e hff, σi
ρ ⊢∆ hif e0 then e1 else e2 , σi →e he2 , σi
2
3.2. ESERCIZI 27

Esercizio 3.6. Dimostrare la validità della seguente implicazione che ga-


rantisce la corrispondenza tra semantica statica e dinamica durante la
computazione chiamata subject reduction.

→e he′ , σi ⇒ ∆ ⊢I e′ : τ
∆ ⊢I e : τ ∧ ρ ⊢∆ he, σi −
S OLUZIONE . Dimostriamo l’implicazione per induzione sulla struttura
della categoria sintattica delle espressioni.

BASE
Consideriamo il caso in cui e = k, a cui possiamo applicare l’assioma

∆ ⊢I k : τ

Poiché non c’è nessuna regola della semantica dinamica applicabile alla
configurazione hk, σi, l’ipotesi dell’implicazione è falsa e quindi l’impli-
cazione è vera.
Consideriamo ora e = x. La regola di semantica statica che usiamo è

∆ ⊢I x : τ, ∆(x) ∈ {τ, τ loc}

Inoltre l’unica regola di semantica dinamica applicabile è

ρ ⊢∆ hx, σi →e hk, σi

dove k = ρ(x) oppure k = σ(ρ(x)). Poiché ρ : ∆ e ∆(x) = τ implica


k = ρ(x) con k ∈ τ (vedi il Capitolo 4, dichiarazione di identificatori
costanti), la semantica statica al valore k non può che assegnare lo stesso
tipo τ

∆ ⊢I k : τ

Se invece ∆(x) = τ loc abbiamo che ρx ∈ Locτ (vedi il Capitolo 4, dichia-


razione di identificatori modificabili) e quindi σ(ρ(x)) ∈ τ da cui ancora
∆ ⊢I k : τ .

PASSO I NDUTTIVO
Supponiamo adesso che l’ipotesi induttiva valga per le espressioni e0 ed e1
28 CAPITOLO 3. ESPRESSIONI

ad esaminiamo il caso in cui e = e0 bop e1 . Per ipotesi tale espressione è


ben formata e ∆ ⊢I e : τ . Questo, però, avviene solo se sia e0 che e1 sono
ben formate ed hanno tipi τ0 e τ1 tali che τbop (τ0 , τ1 ) = τ . Questo per la
regola della semantica statica
∆ ⊢I e0 : τ0 , ∆ ⊢I e1 : τ1
∆ ⊢I e0 bop e1 : τbop (τ0 , τ1 )
Vediamo ora la semantica dinamica, l’espressione e può transire nel caso
in cui venga valutata e0 o nel caso in cui venga valutata e1 . Consideriamo
solo un caso, l’altro è poi analogo. Applichiamo dunque la regola della
semantica dinamica
ρ ⊢∆ he0 , σi →e he′0 , σi
ρ ⊢∆ he0 bop e1 , σi →e he′0 bop e1 , σi
A questo punto vogliamo dimostrare che l’espressione e′0 bop e1 è ben
formata ed ha tipo τbop (τ0 , τ1 ). Questo deriva immediatamente dall’ipotesi
induttiva e′0 : τ0 .
Dobbiamo considerare anche il caso in cui applichiamo l’assioma

ρ ⊢∆ hk bop k ′ , σi −
→e hk ′′ , σi, k ′′ = k bop k ′

Poiché i due argomenti di bop sono ormai completamente valutati, il tipo


che assegnamo a k ′′ è proprio τbop (τ0 , τ1 ).
Del tutto analogo è il caso in cui e = uop e0 , applicando l’ipotesi induttiva
su e0 . 2

Esercizio 3.7. Data un’espressione e ed un ambiente statico ∆, dimostrare


che vale la seguente implicazione

x0 ∈ F I(e) \ Dom(∆) ⇒ ∆ 6⊢I e : τ


S OLUZIONE . La proprietà F I(e) \ Dom(∆) = ∅ garantisce che ogni iden-
tificatore utilizzato in e è stato precedentemente dichiarato oppure è stato
importato nell’ambiente attraverso delle interfacce. In questo senso gene-
ralizziamo il concetto di termine chiuso, solitamente definito solo mediante
3.2. ESERCIZI 29

F I dicendo che un’espressione è chiusa se F I(e) = ∅. La generalizzazio-


ne di chiusura appena introdotta è necessaria per garantire la compilazione
separata dei moduli di un programma. Ciascun modulo suppone dichiarati
gli identificatori che importa dagli altri moduli. Nel seguito parleremo di
termini chiusi riferendoci alla generalizzazione introdotta.

Eseguiamo ora la dimostrazione per induzione sulla struttura delle espres-


sioni.

BASE
Consideriamo e = k. In questo caso si ha che x0 6∈ F I(e) = ∅ dunque
l’implicazione è sempre vera.
Vediamo ora cosa succede se e = x; consideriamo x = x0 perché nell’altro
caso non ci sono problemi essendo l’implicazione sempre vera. La regola
della semantica statica che possiamo applicare è

∆ ⊢I x : τ, ∆(x) ∈ {τ, τ loc}

ma poiché x0 6∈ Dom(∆) per ipotesi tale regola non è applicabile e dun-


que non riusciamo ad assegnare il tipo all’espressione.

PASSO I NDUTTIVO
Supponiamo ora e = e0 bop e1 . Per ipotesi x0 ∈ F I(e) dunque per de-
finizione di F I(e) = F I(e0 ) ∪ F I(e1 ) x0 appartiene almeno a uno dei
due insiemi dell’unione. Senza perdere di genericità possiamo supporre
x0 ∈ F I(e0 ), e quindi per ipotesi induttiva ∆ 6⊢I e0 : τ0 . Possiamo per-
ciò concludere che anche ad e non riusciamo ad assegnare un tipo poiché
l’unica regola che potremmo applicare è

∆ ⊢I e0 : τ0 , ∆ ⊢I e1 : τ1
,
∆ ⊢I e0 bop e1 : τbop

ma una delle due premesse non è verificata.


Il caso in cui e = uop e0 è del tutto analogo. 2
30 CAPITOLO 3. ESPRESSIONI
Capitolo 4

Dichiarazioni

Le dichiarazioni sono una categoria sintattica dei linguaggi di programma-


zione i cui elementi sono elaborati per produrre dei legami. Un legame è
una associazione tra un identificatore ed un valore denotabile (ambiente di-
namico) oppure un identificatore ed un tipo denotabile (ambiente statico).
Secondo questa definizione possiamo pensare agli ambienti come insiemi
di legami.

Le dichiarazioni sono la categoria sintattica dei linguaggi che deter-


mina quindi quali sono le occorrenze libere ed in posizione di definizione
degli identificatori. Un altro concetto fondamentale per questo scopo è
quello di blocco che vedremo nel capitolo sui comandi.

Legato alle dichiarazioni che sono preposte a costruire l’ambiente in


cui eseguire i programmi è anche il concetto di scoping. Questo può essere
statico o dinamico e la sua scelta può determinare esecuzioni con risul-
tati distinti per lo stesso programma. Riprenderemo questo concetto nel
capitolo delle procedure.

Esaminiamo qui le dichiarazioni che sono permesse dalla definizione


di I MP fornendo la loro semantica statica e dinamica. Proseguiamo quindi
fornendo la soluzione di alcuni esercizi proposti.

31
32 CAPITOLO 4. DICHIARAZIONI

4.1 Semantica di D IC
Come nel caso delle espressioni definiamo per prima cosa quali sono gli
identificatori liberi ed in posizione di definizione nelle dichiarazioni.
Definizione 4.1 (identificatori liberi). La funzione F I : Dic → Id che
ad ogni dichiarazione associa l’insieme degli identificatori liberi in essa
contenuti è definita per induzione da

F I(nil) = ∅
F I(const x : τ = e) = F I(var x : τ = e) = F I(e)
F I(d0 ; d1 ) = F I(d0 in d1 ) = F I(d0 ) ∪ (F I(d1 ) \ DI(d0))
F I(d0 and d1 ) = F I(d0 ) ∪ F I(d1 )
F I(ρ) = ∅

Notiamo che la funzione F I : Dic → Id è definita in termini del-


l’analoga funzione per le espressioni che per semplicità ha lo stesso no-
me. Nel testo useremo lo stesso nome per funzioni analoghe su domini
diversi per non appesantire la notazione, ma teniamo presente che in realtà
abbiamo un insieme di funzioni mutuamente ricorsive.
Definizione 4.2 (identificatori in posizione di definizione). La funzione
DI : Dic → Id che ad ogni dichiarazione associa l’insieme degli identifi-
catori in posizione di definizione in essa contenuti è definita per induzione
da

DI(nil) = ∅
DI(const x : τ = e) = DI(var x : τ = e) = {x}
DI(d0 ; d1 ) = DI(d0 and d1 ) = DI(d0) ∪ DI(d1)
DI(d0 in d1 ) = DI(d1 )
DI(ρ) = I, ρ : I

Definiamo adesso la semantica statica il cui scopo è quello di associare


ad ogni dichiarazione un ambiente di tipi che registra il tipo dei valori che
verranno associati agli identificatori durante l’esecuzione dei programmi.
4.1. SEMANTICA DI DIC 33

∆ ⊢I nil : ∅

∆ ⊢I e : τ
∆I ⊢ const x : τ = e : [x = τ ]

∆ ⊢I e : τ
∆I ⊢ var x : τ = e : [x = τ loc]

∆ ⊢I d0 : ∆0 , ∆[∆0 ] ⊢I∪I0 d1 : ∆1
, ∆0 : I0
∆ ⊢I d0 ; d1 : ∆0 [∆1 ]

∆ ⊢I d0 : ∆0 , ∆[∆0 ] ⊢I∪I0 d1 : ∆1
, ∆0 : I0
∆ ⊢I d0 in d1 : ∆1

∆ ⊢I d0 : ∆0 , ∆ ⊢I d1 : ∆1
, I0 ∩ I1 = ∅
∆ ⊢I d0 and d1 : ∆0 , ∆1
Tabella 4.1: Semantica statica per le dichiarazioni di I MP.

Se tale associazione avviene diremo che la dichiarazione è ben formata.


Le regole sono riportate in Tab. 4.1.
Dopo l’assioma per il nil, la prima regola considera le dichiarazioni di
costante. Essa ci dice che dobbiamo valutare l’espressione e per ottenere
il tipo τ corrispondente al valore espresso da e. Se questo tipo coincide
con quello indicato nella dichiarazione stessa si modifica l’ambiente asso-
ciando all’identificatore x il tipo τ . La regola successiva, invece, è per la
dichiarazione di variabili e all’identificatore associa il tipo τ loc. Questo
indica che l’identificatore corrisponde ad una locazione che può contenere
solo un valore di tipo τ . Le tre regole successive servono ad assegnare un
ambiente di tipi a dichiarazioni composte. In particolare, la prima serve
per le dichiarazioni sequenziali per le quali alla fine è visibile l’ambien-
te modificato da entrambe le dichiarazioni d0 e d1 con la proprietà che se
34 CAPITOLO 4. DICHIARAZIONI

ρ ⊢∆ hnil, σi →d h∅, σi

→e he′ , σi
ρ ⊢∆ he, σi −
→d hconst x : τ = e′ , σi
ρ ⊢∆ hconst x : τ = e, σi −

ρ ⊢∆ hconst x : τ = k, σi −
→d h[x = k], σi

→e he′ , σi
ρ ⊢∆ he, σi −
→d hvar x : τ = e′ , σi
ρ ⊢∆ hvar x : τ = e, σi −

ρ ⊢∆ hvar x : τ = k, σi −
→d h[x = l], σ[l = k]i, l = Newτ (L), σ : L
Tabella 4.2: Semantica dinamica per le dichiarazioni semplici di I MP.

un identificatore è definito sia in d0 che in d1 allora è visibile solo l’ul-


tima modifica; per rappresentare l’ambiente risultante si usa la notazione
∆[∆0 ] che si legge ∆ modificato da ∆0 . La seconda è quella delle di-
chiarazioni private dove le modifiche fatte dalla dichiarazione a sinistra
dell’in sono visibili solo all’altra dichiarazione e non all’esterno. Quindi
l’ambiente finale è solo quello associato alla seconda dichiarazione. Infine
la terza regola è quella delle dichiarazioni simultanee dove ogni dichiara-
zione è indipendente dalle modifiche effettuate dall’altra, a condizione che
definiscano identificatori differenti (condizione I0 ∩ I1 = ∅); in tal caso
all’esterno sono visibili entrambi gli ambienti generati dalle dichiarazioni.
In Tab. 4.2 sono riportate le regole per la semantica dinamica che de-
scrivono come vengono elaborate le dichiarazioni semplici considerando
che le configurazioni terminali sono del tipo hρ, σi. Dopo l’assioma per
il nil, le prime due regole servono ad associare alla dichiarazione di co-
stante l’ambiente in cui all’identificatore definito viene associato il valo-
re ottenuto dalla valutazione dell’espressione indicata nella dichiarazione
stessa. Le successive due, invece, servono per elaborare la dichiarazione
di variabile; in tal caso l’ambiente dinamico viene modificato associando
all’identificatore una nuova locazione del tipo richiesto, mentre la memo-
4.1. SEMANTICA DI DIC 35

ria viene modificata associando alla nuova locazione il valore determinato


dalla valutazione dell’espressione presente nella dichiarazione. Il fatto che
la locazione l sia nuova ci viene assicurato dal fatto che l 6∈ L, il dominio
di σ (vedi Def. 2.4).
In Tab. 4.3 abbiamo le regole che permettono di elaborare le dichia-
razioni composte eseguendo una elaborazione da sinistra a destra, e gli
assiomi che indicano come i diversi tipi di composizione di dichiarazio-
ne generano l’ambiente finale a partire dagli ambienti ottenuti elaborando
le due dichiarazioni d0 e d1 componenti. Nel caso delle dichiarazioni se-
quenziali iniziamo elaborando d0 . In seguito elaboriamo d1 nell’ambiente
originale modificato con i legami introdotti da d0 . Infine associamo alla
dichiarazione composta l’ambiente ottenuto unendo gli ambienti associati
alle due dichiarazioni dando priorità, se ci fossero uguali identificatori defi-
niti da entrambe, ai legami introdotti dalla seconda dichiarazione. Per quel
che riguarda le dichiarazioni private si elabora sempre la dichiarazione d0
e, anche in tal caso, viene poi elaborata d1 nell’ambiente modificato da
d0 . Alla fine, però, alla dichiarazione composta viene associato solo l’am-
biente associato a d1 . Nelle dichiarazioni simultanee elaboriamo sempre,
per prima, d0 e poi, però, elaboriamo d1 ancora nell’ambiente originale,
non modificato da d0 . In tal caso alla dichiarazione composta viene asso-
ciato l’ambiente unione dei due ambienti generati, nell’ipotesi che d0 e d1
definiscano identificatori diversi (I0 ∩ I1 = ∅).
Per descrivere il comportamento dinamico delle dichiarazioni definia-
mo una funzione che associa ogni dichiarazione con l’ambiente dinamico
che questa genera.
Definizione 4.3 (elaborazione). La funzione Elab : Dic × Store → Env
che descrive il comportamento dinamico delle dichiarazioni a partire da
una memoria σ restituendo l’ ambiente che esse generano, è definita da
Elab(d, σ) = ρ ⇔ hd, σi →∗d hρ, σ ′ i
A questo punto possiamo utilizzare il comportamento dinamico delle
dichiarazioni per definire una equivalenza sugli elementi di Dic.
Definizione 4.4 (equivalenza). L’equivalenza di dichiarazioni, ≡⊆ Dic ×
Dic, è definita da
d0 ≡ d1 ⇔ ∀σ. (Elab(d0 , σ) = Elab(d1 , σ))
36 CAPITOLO 4. DICHIARAZIONI

→d hd′0 , σ ′ i
ρ ⊢∆ hd0 , σi −
→d hd′0 ; d1 , σ ′ i
ρ ⊢∆ hd0 ; d1 , σi −

→d hd′1 , σ ′ i
ρ[ρ0 ] ⊢∆[∆0] hd1 , σi −
, ρ0 : ∆0
→d hρ0 ; d′1 , σ ′ i
ρ ⊢∆ hρ0 ; d1, σi −

ρ ⊢∆ hρ0 ; ρ1 , σi −
→d hρ0 [ρ1 ], σi

→d hd′0 , σ ′ i
ρ ⊢∆ hd0 , σi −
→d hd′0 in d1 , σ ′ i
ρ ⊢∆ hd0 in d1 , σi −

→d hd′1, σ ′ i
ρ[ρ0 ] ⊢∆[∆0] hd1 , σi −
, ρ0 : ∆0
→d hρ0 in d′1 , σ ′ i
ρ ⊢∆ hρ0 in d1 , σi −

ρ ⊢∆ hρ0 in ρ1 , σi −
→d hρ1 , σi

→d hd′0 , σ ′ i
ρ ⊢∆ hd0 , σi −
→d hd′0 and d1 , σ ′ i
ρ ⊢∆ hd0 and d1 , σi −

→d hd′1 , σ ′ i
ρ ⊢∆ hd1 , σi −
→d hρ0 and d′1 , σ ′ i
ρ ⊢∆ hρ0 and d1 , σi −

ρ ⊢∆ hρ0 and ρ1 , σi −
→d hρ0 , ρ1 , σi
Tabella 4.3: Semantica dinamica per le dichiarazioni composte di I MP.
4.2. ESERCIZI 37

4.2 Esercizi
Esercizio 4.1. Aggiungere ad I MP la dichiarazione
d ::= x == y
il cui significato è quello di associare x alla stessa locazione di y. Definir-
ne semantica statica e dinamica.
S OLUZIONE . Cerchiamo, prima di tutto, di capire di che cosa si tratta.
Dopo tale dichiarazione si ottengono due identificatori differenti che però
sono associati alla stessa locazione, si verifica cioè un fenomeno chiama-
to di aliasing a causa del quale possiamo modificare il contenuto di una
locazione mediante due identificatori che esistono indipendentemente l’u-
no dall’altro. Definiamo gli identificatori liberi e quelli in posizione di
definizione della nuova dichiarazione:
F I(x == y) = {y}
DI(x == y) = {x}
Semantica statica
Poiché l’identificatore x che stiamo definendo deve riferirsi alla stessa
locazione associata a y, dovrà avere lo stesso tipo
∆ ⊢I y : τ loc
∆ ⊢I (x == y) : [x = τ loc]
Semantica dinamica
L’unica operazione da fare è quella di associare la locazione che corrispon-
de a y anche a x.
ρ ⊢∆ hx == y, σi →d hρ[x = l], σi , l = ρ(y)
2

Esercizio 4.2. Sostituire la dichiarazione var x : τ = e con


d ::= var x : τ
che non esegue l’inizializzazione di x fino al primo assegnamento che
coinvolge l’identificatore.
38 CAPITOLO 4. DICHIARAZIONI

S OLUZIONE . Definiamo prima gli identificatori liberi ed in posizione di


definizione nella nuova dichiarazione

F I(var x : τ ) = ∅
DI(var x : τ ) = {x}

Semantica statica
Si nota che quando dichiariamo l’identificatore dobbiamo solo assegnargli
un tipo senza nessun controllo

∆ ⊢I (var x : τ ) : [x = τ loc]

Semantica dinamica
Nella semantica dinamica dobbiamo controllare cosa succede nelle regole
in cui vengono aggiornati gli identificatori: dichiarazione e assegnamento.
In particolare, in questo caso, cambia solo la dichiarazione.
Per essa infatti dobbiamo trovare un modo per poter individuare a tempo
di esecuzione quali identificatori sono stati dichiarati ma non inizializzati,
in modo da poter generare un errore, se necessario; usiamo per tale scopo
il simbolo ⊥.

ρ ⊢∆ hvar x : τ, σi →d h[x = l], σ[l = ⊥]i, l ∈ Newτ (L), σ : L

Però a questo punto bisogna modificare la regola di valutazione degli iden-


tificatori aggiungendo la condizione che x sia stato inizializzato assegnan-
do un valore diverso da ⊥ alla locazione l corrispondente

ρ ⊢∆ hx, σi →e hk, σi, (k = ρ(x) ∨ (l = ρ(x) ∧ k = σ(l) ∧ k 6= ⊥))

Esercizio 4.3. Definire semantica statica e dinamica della dichiarazione

d ::= perv x : τ

con semantica intuitiva che x non può essere dichiarata di nuovo dentro
un blocco contenuto in quello in cui appare la dichiarazione d.
4.2. ESERCIZI 39

S OLUZIONE . Aggiungiamo al linguaggio I MP la dichiarazione fornita dal


testo dell’esercizio. Gli identificatori liberi e gli identificatori in posizione
di definizione sono:

F I(perv x : τ ) = ∅
DI(perv x : τ ) = {x}

Anche in questo caso dobbiamo essere in grado di distinguere a tempo di


esecuzione quali variabili sono state dichiarate normalmente e quali sono
perv. Intrduciamo allora un nuovo tipo denotabile:

DT yp ::= . . . | τ perv

Semantica statica
Vediamo come funziona la semantica statica per la nuova dichiarazione

∆ ⊢I (perv x : τ ) : [x = τ perv], x 6∈ I|perv

dove I|perv = {x ∈ I | ∆(x) = τ perv}. La condizione indica che l’iterse-


zione tra gli identificatori su cui è definito ∆, ristrette al caso in cui siano
di tipo τ perv, e x deve essere vuota proprio perché la semantica intuitiva
della dichiarazione dice che gli identificatori definiti perv non possono
essere ridefiniti.
Analogamente dobbiamo aggiungere la condizione x 6∈ I|perv anche alle
regole per le dichiarazioni di tipo const e var per impedire ridefinizioni.
Non dobbiamo modificare le regole per le dichiarazioni composte poiché
la modifica dei casi base garantisce la proprietà per tutte le dichiarazio-
ni. Questo possiamo dimostrarlo per induzione sulla forma degli alberi di
derivazione. Consideriamo ad esempio la regola per la dichiarazione se-
quenziale. Negli altri casi è analogo. Se in d0 e d1 non vengono ridefiniti
gli identificatori dichiarati perv, questo non accadrà neppure in d0 ; d1 . In-
fatti per ipotesi induttiva x 6∈ I|perv in ∆ ⊢I d0 : ∆0 per ogni dichiarazione
di x in d0 . Ma ogni dichiarazione di x in ∆[∆0 ] ⊢I∪I0 d1 : ∆1 verifica
per ipotesi induttiva x 6∈ (I ∪ I0 )|perv e quindi le dichiarazioni in d0 ; d1
verificano la proprietà.
Semantica dinamica
Consideriamo il linguaggio I MP modificato nell’Esercizio 4.2; a questo
40 CAPITOLO 4. DICHIARAZIONI

aggiungiamo le nuove regole


ρ ⊢∆ hperv x : τ, σi →d h[x = l], σ[l = ⊥]i,
ρ(x) 6∈ Locτ perv ∧ l ∈ Newτ perv (L), σ : L
Tale regola associa l’identificatore ad una locazione se esso non era pre-
cedentemente stato dichiarato nello stesso blocco come perv. A questo
punto, avendo modificato i casi base delle dichiarazioni non c’è bisogno di
modificare le regole di inferenza per le dichiarazioni composte.
Notiamo che la semantica statica ci assicura che le dichiarazioni che ela-
boriamo non ridefiniscono gli identificatori di tipo perv nei blocchi che le
contengono. 2

Esercizio 4.4. Definire semantica statica e dinamica della seguente di-


chiarazione
d ::= external x : τ
con semantica intuitiva: se dentro un blocco un identificatore è definito
external allora viene inizializzato con il valore che aveva fuori dal blocco.
S OLUZIONE . La prima considerazione da fare è che per poter inserire que-
sto tipo di modifica bisogna fare riferimento al linguaggio I MP modificato
nell’Esercizio 4.2 che non inizializza gli identificatori quando li dichiara.
Prima di definire la semantica del nuovo costrutto aggiungiamo un tipo
denotabile per distinguere gli identificatori external dagli altri
DT yp ::= . . . | τ ext
Notiamo inoltre che per questa nuova dichiarazione gli insiemi FI e DI
sono gli stessi insiemi definiti per tutti gli altri tipi di dichiarazione.
Semantica statica
La semantica statica per la dichiarazione external è
∆ ⊢I (external x : τ ) : [x = τ ext]
Quindi, in base alla semantica intuitiva fornita dal testo, a livello statico le
dichiarazioni external si comportano esattamente come le altre associando
semplicemente il tipo corretto alla variabile.
4.2. ESERCIZI 41

Semantica dinamica

ρ ⊢∆ hexternal x : τ, σi →d h[x = l], σ[l = ⊥]i,


x 6∈ Dom(ρ) ∨ ρ(x) = l⊥ , l = Newτ (L), σ : L

cioè se x non è già stata dichiarata (e/o inizializzata) la dichiarazione ex-


ternal è equivalente alle altre dichiarazioni. Invece se x era già stata ini-
zializzata la allochiamo nuovamente dandole, però, il valore che aveva
precedentemente.

ρ ⊢∆ hexternal x : τ, σi →d h[x = l], σ[l = k]i,


x ∈ Dom(ρ) ∧ k = σ(l′ ) ∧ l′ = ρ(x) 6= l⊥ ,
l = Newτ (L), σ : L

Si noti che in tal modo creiamo una variabile del tutto identica a quella
esterna con l’effetto, però, che, uscendo dal blocco, la variabile esterna è
esattamente come era prima dell’attivazione del blocco, pur avendo lavo-
rato con il valore che aveva (l’entrata nel blocco simula una chiamata di
procedura con passaggio dei parametri per valore; vedi Capitolo 6).
A questo punto, non avendo alterato l’effetto della dichiarazione sui co-
mandi successivi, non ci sono altre modifiche da fare.
Nella semantica intuitiva delle dichiarazioni external la frase “viene
inizializzato con il valore che aveva fuori dal blocco” si può interpretare
come deve avere un valore fuori dal blocco anziché può avere un valore
come abbiamo fatto nella soluzione proposta. Nel caso del deve la seman-
tica statica deve garantire che x sia stato già dichiarato al momento della
sua dichiarazione external e quindi avremmo

∆ ⊢I (external x : τ ) : [x = τ ext], ∆(x) ∈ {τ, τ loc, τ ext}

La semantica dinamica può ora essere definita utilizzando solo il secondo


assioma. Infatti x ∈ Dom(ρ) nella condizione del secondo assioma è ga-
rantito dalla semantica statica, mentre ρ(x) = l⊥ nel primo assioma deve
generare un errore a tempo di esecuzione in quanto l’identificatore x non
era stato inizializzato.
Questo è un esempio in cui diversi implementatori del costrutto potrebbe-
ro fare assunzioni diverse e quindi realizzazioni diverse con la nascita di
42 CAPITOLO 4. DICHIARAZIONI

numerosi dialetti dello stesso linguaggio. Uno degli scopi della semantica
formale dei linguaggi è proprio quello di evitare le ambiguità del linguag-
gio naturale per garantire che tutte le relizzazioni dello stesso linguaggio
siano identiche con notevole beneficio per la portabilità, manutenibilità e
chiarezza delle applicazioni. 2

Esercizio 4.5. Estendere I MP con il blocco per le espressioni la cui sintassi


e ::= let d into e

con semantica intuitiva: valutare l’espressione e nell’ambiente corrente


esteso con i legami generati da d. Definire semantica statica e dinamica.
Dimostrare poi che l’espressione sotto è corretta dal punto di vista dei tipi
let const x : int = 3
into let const x : int = 5 & const y : int = 6 ∗ x
into x + y
dove & può essere ; (per le dichiarazioni sequenziali), and (per le dichia-
razioni simultanee) oppure in (per le dichiarazioni private). Determinare
quindi il risultato delle valutazioni dell’espressione nel caso in cui & sia

i.) ; ii.) and iii) in

fornendo le derivazioni principali delle transizioni delle valutazioni.


S OLUZIONE . Partiamo inserendo la nuova espressione nel nostro linguag-
gio e definendo gli identificatori liberi e in posizione di definizione

F I(let d into e) = (F I(e) \ DI(d)) ∪ F I(d)


DI(let d into e) = DI(d) ∪ DI(e)

Poiché nelle espressioni ci possono essere identificatori in posizione di de-


finizione, bisogna modificare DI in modo che, se applicato ad una espres-
sione, ad un comando o ad una dichiarazione contenente una espressione,
tenga conto anche degli identificatori in essa definiti (es. DI(x := e) =
DI(e), . . .).
4.2. ESERCIZI 43

Semantica statica
Dobbiamo valutare il tipo di e nell’ambiente di tipi modificato dalla di-
chiarazione d e assegnamo il tipo di e alla nuova espressione
∆ ⊢I d : ∆0 , ∆[∆0 ] ⊢I∪I0 e : τ
, ∆0 : I0 (4.1)
∆ ⊢I (let d into e) : τ
Semantica dinamica
Con questa nuova espressione anche la valutazione delle espressioni causa
modifiche nella memoria generando side-effects. Quindi dobbiamo mo-
dificare le regole in Tab. 3.3 considerando che ogni volta che valutiamo
una epressione possiamo modificare anche la memoria. Per quanto riguar-
da il blocco, prima valutiamo la dichiarazione d e poi procediamo con
la valutazione di e assegnando, infine, il valore associato ad e all’intera
espressione
ρ ⊢∆ hd, σi →d hd′ , σ ′ i
ρ ⊢∆ hlet d into e, σi →e hlet d′ into e, σ ′ i

ρ[ρ0 ] ⊢∆[∆0] he, σi →e he′ , σ ′ i


, ρ0 : ∆0
ρ ⊢∆ hlet ρ0 into e, σi →e hlet ρ0 into e′ , σ ′ i

ρ[ρ0 ] ⊢∆[∆0 ] he, σi →e hk, σ ′ i


, ρ0 : ∆0
ρ ⊢∆ hlet ρ0 into e, σi →e hk, σ ′ i
Vista la semantica della nuova espressione passiamo alla dimostrazione
che il frammento fornito è corretto dal punto di vista dei tipi. Per maggiore
chiarezza e semplicità conviene dare dei nomi alle varie dichiarazioni e
sottoespressioni
d1 = const x : int = 3
d2 = const x : int = 5
d3 = const y : int = 6 ∗ x
e1 = x + y
e2 = let d1 into let d2 & d3 into e1
44 CAPITOLO 4. DICHIARAZIONI

Vediamo ora nei differenti casi cosa succede assumendo di aver già deter-
minato il tipo delle espressioni di inizializzazione.

i. Consideriamo & = ;. Dobbiamo usare la regola (4.1) partendo da un


ambiente di tipi iniziale ∆ : I e dalla dichiarazione d1 i cui legami
serviranno per l’analisi di d2 ; d3

∆ ⊢I d1 : [x = int]

Bisogna poi assegnare il tipo ad e2 in ∆[x = int] analizzando ini-


zialmente la dichiarazione d2 ;d3

∆[x = int] ⊢I∪{x} d2 : [x = int],


(∆[x = int])[x = int] ⊢I∪{x} d3 : [y = int]
∆[x = int] ⊢I∪{x} (d2 ;d3 ) : [x = int][y = int]

Valutiamo e1 in ∆[x = int, y = int] (= ∆[x = int][y = int] visto


che le due modifiche riguardano identificatori diversi)

∆[x = int, y = int] ⊢I∪{x,y} x : int,


∆[x = int, y = int] ⊢I∪{x,y} y : int
∆[x = int, y = int] ⊢I∪{x,y} (x + y) : int

Quindi unendo le due valutazioni fatte

∆[x = int] ⊢I∪{x} (d2 ;d3 ) : [x = int, y = int],


∆[x = int, y = int] ⊢I∪{x,y} e1 : int
∆[x = int] ⊢I∪{x} e2 : int

Infine possiamo concludere

∆ ⊢I d1 : [x = int], ∆[x = int] ⊢I∪{x} e2 : int


∆ ⊢I (let d1 into e2 ) : int

e quindi dal punto di vista statico non ci sono problemi.


4.2. ESERCIZI 45

ii. Consideriamo & = and. Rispetto al caso precedente cambia solo


la valutazione dell’espressione e2 sempre nell’ambiente ∆[x = int].
Valutiamo inizialmente d2 and d3

∆[x = int] ⊢I∪{x} d2 : [x = int], ∆[x = int] ⊢I∪{x} d3 : [y = int]


∆[x = int] ⊢I∪{x} (d2 and d3 ) : [x = int], [y = int]

Poiché e1 deve essere valutato nell’ambiente in cui è stato valutato


precedentemente, anche in questo caso non ci sono problemi.

iii. Consideriamo & = in. Anche qui dobbiamo valutare solo e2 nel-
l’ambiente ∆[x = int] perché il resto è analogo ai casi precedenti.
Valutiamo d2 in d3

∆[x = int] ⊢I∪{x} d2 : [x = int],


(∆[x = int])[x = int] ⊢I∪{x} d3 : [y = int]
∆[x = int] ⊢I∪{x} (d2 in d3 ) : [y = int]

Ma allora per attribuire un tipo a e1 dobbiamo valutarla nell’am-


biente (∆[x = int])[y = int] che è esattamente l’ambiente in cui
abbiamo valutato l’espressione nei casi precedenti e dunque anche
qui non ci sono problemi.

Dobbiamo infine determinare il risultato della valutazione dell’espressione


nei tre diversi casi usando, per semplicità, le stesse notazioni usate prima.

i. Consideriamo & = ;. Valutiamo subito d1 nell’ambiente iniziale ρ,


compatibile con l’ambiente di tipi ∆, e una memoria iniziale σ

ρ ⊢∆ hconst x = 3, σi →d h[x = 3], σi

Vediamo ora che valore viene attribuito ad e2 nell’ambiente ρ[x = 3]


compatibile con ∆′ = ∆[x = int]

ρ[x = 3] ⊢∆′ hconst x = 5, σi →d h[x = 5], σi


ρ[x = 3] ⊢∆′ hconst x = 5 ; const y = 6 ∗ x, σi →d
h[x = 5]; const y = 6 ∗ x, σi
46 CAPITOLO 4. DICHIARAZIONI

(ρ[x = 3])[x = 5] ⊢∆′ hx, σi →e h5, σi


(ρ[x = 3])[x = 5] ⊢∆′ h6 ∗ x, σi →e h30, σi
ρ[x = 3] ⊢∆′ h[x = 5]; const y = 6 ∗ x, σi →d h[x = 5][y = 30], σi

Dobbiamo ora valutare e1 in (ρ[x = 3])[x = 5, y = 30] compatibile


con ∆′′ = ∆′ [y = int]

(ρ[x = 3])[x = 5, y = 30] ⊢∆′′ hx, σi →e h5, σi


(ρ[x = 3])[x = 5, y = 30] ⊢∆′′ hx + y, σi →e h5 + y, σi
ed infine
(ρ[x = 3])[x = 5, y = 30] ⊢∆′′ hy, σi →e h30, σi
(ρ[x = 3])[x = 5, y = 30] ⊢∆′′ h5 + y, σi →e h35, σi
ρ[x = 3] ⊢∆′ he2 , σi →e h35, σi
ρ ⊢∆ hlet d1 into e2 , σi →e h35, σi

Da cui il valore finale 35 dell’espressione.

ii. Consideriamo & = and. Vediamo ora solo le transizioni che sono
diverse rispetto al caso precedente; questa volta y non viene valutato
nell’ambiente modificato da d2 poichè d2 e d3 sono simultanee

ρ[x = 3] ⊢∆′ hx, σi →e h3, σi


ρ[x = 3] ⊢∆′ h6 ∗ x, σi →e h18, σi
ρ[x = 3] ⊢∆′ h[x = 5] and const y = 6 ∗ x, σi →d
h[x = 5], [y = 18], σi

Valutiamo e1 in (ρ[x = 3])[x = 5, y = 18] (= ρ[x = 5], [y = 18])


compatibile con ∆′′ = ∆′ [y = int]

(ρ[x = 3])[x = 5, y = 18] ⊢∆′′ hx, σi →e h5, σi


(ρ[x = 3])[x = 5, y = 18] ⊢∆′′ hx + y, σi →e h5 + y, σi
4.2. ESERCIZI 47

ed infine

(ρ[x = 3])[x = 5, y = 18] ⊢∆′′ hy, σi →e h18, σi


(ρ[x = 3])[x = 5, y = 18] ⊢∆′′ h5 + y, σi →e h23, σi
ρ[x = 3] ⊢∆′ he2 , σi →e h23, σi
ρ ⊢∆ hlet d1 into e2 , σi →e h23, σi

Da cui il valore finale 23.

iii. Consideriamo & = in. Anche qui vediamo solo le transizioni che
cambiano

(ρ[x = 3])[x = 5] ⊢∆′ hx, σi →e h5, σi


(ρ[x = 3])[x = 5] ⊢∆′ h6 ∗ x, σi →e h30, σi
ρ[x = 3] ⊢∆′ h[x = 5] in const y = 6 ∗ x, σi →d h[y = 30], σi

Questa volta dobbiamo valutare e1 in ρ[x = 3][y = 30], in quanto


all’esterno della valutazione della dichiarazione si vede solo l’am-
biente generato da d3

(ρ[x = 3])[y = 30] ⊢∆′′ hx, σi →e h3, σi


(ρ[x = 3])[y = 30] ⊢∆′′ hx + y, σi →e h3 + y, σi

ed infine

(ρ[x = 3])[y = 30] ⊢∆′′ hy, σi →e h30, σi


(ρ[x = 3])[y = 30] ⊢∆′′ h3 + y, σi →e h33, σi
ρ[x = 3] ⊢∆′ he2 , σi →e h33, σi
ρ ⊢∆ hlet d1 into e2 , σi →e h33, σi

Da cui il valore finale 33.

2
48 CAPITOLO 4. DICHIARAZIONI

Esercizio 4.6. Dimostrare che vale la seguente implicazione


∆ ⊢I d : ∆′ ∧ ρ ⊢∆ hd, σi →d hd′ , σ ′ i ⇒ ∆ ⊢I d′ : ∆′
Poiché la categoria sintattica delle espressioni è usata da quella delle
dichiarazioni, assumiamo il risultato dimostrato nell’Esercizio 3.6.
S OLUZIONE . Dimostriamo l’implicazione per induzione sulla struttura
della categoria sintattica delle dichiarazioni.

BASE
Consideriamo il caso in cui d = const x : τ = e. Per ipotesi la dichiara-
zione è ben formata e ∆ ⊢I d : [x = τ ]. Allora anche l’espressione e è ben
formata ed ha tipo τ per la regola della semantica statica
∆ ⊢I e : τ
(4.2)
∆ ⊢I (const x : τ = e) : [x = τ ]
La regola della semantica dinamica che possiamo applicare è
ρ ⊢∆ he, σi →e he′ , σi
ρ ⊢∆ hconst x : τ = e, σi →d hconst x : τ = e′ , σi
Vogliamo dunque vedere se la dichiarazione d′ = const x : τ = e′ è ben
formata e la semantica statica le associa l’ambiente statico ∆′ = [x = τ ].
Applicando la regola (4.2) a d′ vediamo che è possibile associarle un am-
biente solo se e′ è ben formata ed ha tipo τ . Ma questo è vero per il risultato
dimostrato nell’Esercizio 3.6.
Il caso in cui d = var x : τ = e è analogo a quello appena visto.

PASSO I NDUTTIVO
Supponiamo che l’ipotesi induttiva valga per le dichiarazioni d0 e d1 ed
esaminiamo la dichiarazione composta d = d0 ; d1. Per ipotesi tale dichia-
razione è ben formata, quindi la semantica statica riesce ad assegnarle un
ambiente statico ∆′ = ∆0 [∆1 ]. Ma allora lo sono anche d0 e d1 perché se
almeno una delle due non lo fosse non lo sarebbe neanche d per la regola
∆ ⊢I d0 : ∆0 , ∆[∆0 ] ⊢I d1 : ∆1
(4.3)
∆ ⊢I (d0 ; d1) : ∆0 [∆1 ]
4.2. ESERCIZI 49

Per quel che riguarda la semantica dinamica, la transizione che può fare
una tale dichiarazione consiste in una transizione di d0 o di d1 . Vediamo
cosa succede solo quando transisce d0 , l’altro caso è analogo.
ρ ⊢∆ hd0 , σi →d hd′0 , σ ′ i
ρ ⊢∆ hd0 : d1 , σi →d hd′0 ; d1 , σ ′ i
Vogliamo vedere allora se ∆ ⊢I d′0 ; d1 : ∆′ . Applicando la regola (4.3)
a d′ = d′0 ; d1 questo vale se sono ben formate sia d′0 che d1 e generano
ambienti statici ∆0 e ∆1 rispettivamente. Ma d1 : ∆1 per ipotesi e d′0 : ∆0
per ipotesi induttiva.
Ci rimane da considerare il caso in cui applichiamo l’assioma

ρ ⊢∆ hρ0 ; ρ1 , σi −
→d hρ0 [ρ1 ], σi

Poiché ρ0 : ∆0 e ρ1 : ∆1 abbiamo ρ0 [ρ1 ] : ∆0 [∆1 ] = ∆′ .


Nel caso di dichiarazioni simultanee e private la dimostrazione è analoga.
2

Esercizio 4.7. Data una dichiarazione d ed un anbiente statico ∆, dimo-


strare la seguente implicazione

x0 ∈ F I(d) \ Dom(∆) ⇒ ∆ 6⊢I d : ∆′

assumendo il risultato dell’Esercizio 3.7.


S OLUZIONE . Dimostriamo per induzione, quindi, che non riusciamo ad
associare un ambiente alla dichiarazione nel caso ci trovassimo nelle ipo-
tesi dell’implicazione.

BASE
Consideriamo d = const x : τ = e. In tal caso, x0 ∈ F I(d) implica che,
per definizione di F I(d), x0 ∈ F I(e). Infatti x 6= x0 poiché x ∈ DI(d).
L’unica regola che potremmo applicare per determinare l’ambiente statico
da associare a d è
∆ ⊢I e : τ
∆ ⊢I constx : τ = e : [x = τ ]
50 CAPITOLO 4. DICHIARAZIONI

Ma per quanto dimostrato nell’Esercizio 3.7 non riusciamo ad assegnare


un tipo ad e e di conseguenza non possiamo applicare la regola poiché la
premessa è falsa.
Il caso in cui d = var x : τ = e è analogo a quello appena dimostrato.

PASSO I NDUTTIVO
Consideriamo la dichiarazione composta d = d0 ; d1 . Per ipotesi x0 ∈
F I(d0 ; d1 ) = F I(d0 ) ∪ (F I(d1 ) \ DI(d0 )) . Questo significa che x0 ap-
partiene alle variabili libere di almeno una delle due dichiarazioni compo-
nenti e nel caso occorra in d1 non viene dichiarata in d0 . Senza perdere
di generalità possiamo supporre che x0 ∈ F I(d0 ). Per ipotesi induttiva
non riusciamo ad associare un ambiente a d0 , e dunque non riusciamo ad
associarlo neanche a d poiché l’unica regola utile

∆ ⊢I d0 : ∆0 , ∆[∆0 ] ⊢I∪I0 d1 : ∆1
, ∆0 : I0
∆ ⊢I d0 ; d1 : ∆0 [∆1 ]

ha una delle due premesse non verificata. Si noti che il fatto che d1 venga
elaborata in un ambiente più ampio non crea problemi alla dimostrazione
perché per ipotesi x0 6∈ DI(d0) e dunque l’ipotesi induttiva vale anche per
d1 che viene elaborata nell’ambiente ∆[∆0 ] tale che x0 6∈ Dom(∆[∆0 ]).
Nei casi di composizione simultanea e privata la dimostrazione è analoga.
2
Capitolo 5

Comandi

I comandi sono una categoria sintattica dei linguaggi imperativi i cui ele-
menti sono eseguiti per aggiornare la memoria della macchina astratta che
implementa il supporto del linguaggio. I comandi sono la categoria sin-
tattica che distingue il paradigma imperativo da quello funzionale (e più
in generale da tutti gli altri). Infatti la parola imperativo deriva dal latino
imperare che vuol dire comandare. Poiché lo scopo dei comandi è quello
di aggiornare la memoria, la loro esecuzione non restituisce nessun valore.
I comandi sono costituiti a partire dall’assegnamento che modifica il
contenuto di una locazione di memoria e poi contengono costrutti per la
composizione ed il controllo del flusso di esecuzione.
Esaminiamo qui i comandi che compaiono nella definizione di I MP for-
nendo la loro semantica statica e dinamica. Proseguiamo quindi fornendo
la soluzione di alcuni esercizi proposti.

5.1 Semantica di C OM
Come per le altre categorie sintattiche definiamo, per prima cosa, gli iden-
tificatori liberi e in posizione di definizione che occorrono nei comandi.

Definizione 5.1 (identificatori liberi). La funzione F I : Com → Id,


che ad ogni comando associa l’insieme degli identificatori liberi in esso

51
52 CAPITOLO 5. COMANDI

contenuti, è definita per induzione da

F I(nil) = ∅
F I(x := e) = F I(e) ∪ {x}
F I(c0 ; c1 ) = F I(c0 ) ∪ F I(c1 )
F I(if e then c0 else c1 ) = F I(c0 ) ∪ F I(c1 ) ∪ F I(e)
F I(while e do c) = F I(c) ∪ F I(e)
F I(d; c) = F I(d) ∪ (F I(c) \ DI(d))

Definizione 5.2 (identificatori in posizione di definizione). La funzione


DI : Com → Id, che ad ogni comando associa l’insieme degli identifica-
tori in posizione di definizione in esso contenuti, è definita per induzione
da

DI(nil) = ∅
DI(x := e) = ∅
DI(c0 ; c1 ) = DI(c0 ) ∪ DI(c1 )
DI(if e then c0 else c1 ) = DI(c0 ) ∪ DI(c1)
DI(while e do c) = DI(c)
DI(d; c) = DI(c) ∪ DI(d)

Come nei capitoli precedenti, commentiamo adesso la semantica stati-


ca spiegando le regole in Tab. 5.1.
In generale il compito della semantica statica è quello di vedere se un
comando è ben formato; questo avviene nel caso in cui le sue componenti
sono ben formate, cioè quando alle espressioni componenti riusciamo ad
assegnare un tipo, e alle dichiarazioni riusciamo ad assegnare un ambiente
statico; in tal modo deduciamo la ben formatezza del comando. Vedia-
mo, dunque, i comandi uno per volta. Il nil non ha sottocomponenti e per
questo è sempre ben formato. Per quel che riguarda l’assegnamento, le
condizioni perché sia ben formato è che l’espressione e abbia lo stesso ti-
po dell’identificatore x che deve essere stato dichiarato nell’ambiente ∆.
5.1. SEMANTICA DI COM 53

∆ ⊢I e : τ
∆ ⊢I nil , ∆(x) = τ loc
∆ ⊢I x := e

∆ ⊢ I c0 , ∆ ⊢ I c1 ∆ ⊢I e : bool, ∆ ⊢I c0 , ∆ ⊢I c1
∆ ⊢ I c0 ; c1 ∆ ⊢I if e then c0 else c1

∆ ⊢I e : bool, ∆ ⊢I c ∆ ⊢I d : ∆′ , ∆[∆′ ] ⊢I∪I ′ c


, ∆′ : I ′
∆ ⊢I while e do c ∆ ⊢I d; c
Tabella 5.1: Semantica statica per i comandi di I MP.

Invece la sequenzializzazione c1 ; c2 è ben formata se lo sono i comandi che


la compongono. I comandi if e while, sono ben formati se l’espressione
guardia e, in entrambi, ha tipo booleano e se i sottocomandi che conten-
gono sono ben formati. Infine per la semantica del blocco, si ha che d; c è
ben formato se elaborando d nell’ambiente di tipi iniziale ∆ riusciamo ad
assegnargli un ambiente ∆′ e se il comando c è ben formato quando viene
valutato in ∆ modificato dall’ambiente ∆′ generato da d.
Esaminiamo ora le regole della semantica dinamica per i comandi ri-
portate in Tab. 5.2 considerando che un comando può transire in configura-
zioni del tipo hc, σi oppure σ, dove quest’ultima è terminale. Nel caso del-
l’assegnamento, appena valutata l’espressione e, modifichiamo la memoria
associando il valore ottenuto da e alla locazione che l’ambiente ρ associa
all’identificatore considerato. Il comando nil è, invece, un comando neutro
che non ha effetto sulla memoria. Il comando if è un comando condiziona-
le, dunque a seconda del valore booleano dell’espressione e viene eseguito
il comando del ramo then o quello del ramo else. In particolare se e vale
tt allora il sistema transisce nella configurazione che permette di eseguire
il comando posizionato dopo then; se invece e vale ff allora si esegue l’al-
tro comando. Per quel che riguarda il comando c1 ; c2 viene inizialmente
eseguito c1 . Dopo, nella memoria modificata dal primo comando, viene
eseguito il secondo comando c2 . Il while è, invece, un comando iterativo
54 CAPITOLO 5. COMANDI

→∗e hk, σi
ρ ⊢∆ he, σi −
, ρ(x) = l ρ ⊢∆ hnil, σi −
→c σ
ρ ⊢∆ hx := e, σi −
→c σ[l = k]

→∗e hff, σi
ρ ⊢∆ he, σi − →c hc′0 , σ ′ i
ρ ⊢∆ hc0 , σi −
ρ ⊢∆ hif e then c0 else c1 , σi −
→c hc1 , σi →c hc′0 ; c1 , σ ′ i
ρ ⊢∆ hc0 ; c1 , σi −

→∗e htt, σi
ρ ⊢∆ he, σi − →c σ ′
ρ ⊢∆ hc0 , σi −
ρ ⊢∆ hif e then c0 else c1 , σi −
→c hc0 , σi →c hc1 , σ ′ i
ρ ⊢∆ hc0 ; c1 , σi −

→∗e htt, σi
ρ ⊢∆ he, σi −
ρ ⊢∆ hwhile e do c, σi −
→c hc; while e do c, σi

→∗e hff, σi
ρ ⊢∆ he, σi −
ρ ⊢∆ hwhile e do c, σi −
→c σ

→d hd′ , σ ′ i
ρ ⊢∆ hd, σi − →c hc′ , σ ′ i
ρ[ρ0 ] ⊢∆[∆0 ] hc, σi −
, ρ0 : ∆0
→c hd′ ; c, σ ′i
ρ ⊢∆ hd; c, σi − →c hρ0 ; c′ , σ ′ i
ρ ⊢∆ hρ0 ; c, σi −

→c σ ′
ρ[ρ0 ] ⊢∆[∆0 ] hc, σi −
, ρ0 : ∆0
→c σ ′
ρ ⊢∆ hρ0 ; c, σi −
Tabella 5.2: Semantica dinamica per i comandi di I MP.
5.2. ESERCIZI 55

che esegue il corpo c fino al momento in cui la condizione in esso conte-


nuta diventa falsa. Quindi, nel caso in cui la valutazione di e sia tt allora il
comando transisce nella configurazione in cui si ha il comando c da esegui-
re e nuovamente il while che permette di iterare l’esecuzione del comando
c. Se la valutazione di e è ff, invece, il comando non fa nulla e restistuisce
la memoria inalterata. Il comando while viene anche chiamato di itera-
zione indefinita perché non è noto a priori il numero di volte in cui viene
eseguito il corpo c. Questo per contrapporlo a comandi stile for chiamati
di iterazione finita per i quali invece il numero di volte in cui viene eseguito
il corpo è noto all’inizio dell’esecuzione (vedi Esercizio 5.8). Infine, per
ciò che riguarda il blocco, si valuta inizialmente la dichiarazione d e poi si
esegue c nell’ambiente iniziale modificato da quello generato da d.
Per formalizzare il comportamento dinamico dei comandi introducia-
mo una funzione che restituisce la memoria ottenuta dopo l’esecuzione di
un comando.

Definizione 5.3 (esecuzione). La funzione Exec : Com×Store → Store,


che descrive il comportamento dinamico dei comandi a partire da una
memoria σ restituendo la memoria della configurazione finale, è definita
da

Exec(c, σ) = σ ′ ⇔ hc, σi →∗c σ ′

A questo punto possiamo utilizzare il comportamento dinamico dei


comandi per definire una equivalenza sugli elementi di Com.

Definizione 5.4 (equivalenza). L’equivalenza di comandi ≡ ⊆ Com ×


Com è definita da

c0 ≡ c1 ⇔ ∀σ. (Exec(c0 , σ) = Exec(c1 , σ))

5.2 Esercizi
Esercizio 5.1. Definire una funzione Mod : Com → Id che associa ad
ogni comando l’insieme degli identificatori che possono potenzialmente
essere modificati.
56 CAPITOLO 5. COMANDI

S OLUZIONE . L’unico comando che può alterare il contenuto di una loca-


zione associata ad un identificatore è l’assegnamento. Poiché questo può
comparire in molti contesti sintattici distinti, procediamo per induzione
strutturale sulla sintassi dei comandi partendo dalle basi nil e x := e.
Mod(nil) = ∅
Mod(id := e) = {id}
A questo punto applico il passo induttivo definendo Mod sui comandi
composti.
Mod(c1 ; c2 ) = Mod(c1 ) ∪ Mod(c2 )
Mod(if e then c1 else c2 ) = Mod(c1 ) ∪ Mod(c2 )
Mod(while e do c) = Mod(c)
Mod(d; c) = Mod(c)
Nel caso del blocco, gli identificatori dichiarati in d non sono considerati
modificabili poiché essi non saranno più visibili all’uscita del blocco. 2

Esercizio 5.2. Definire la semantica statica e dinamica del nuovo coman-


do x+ := e in modo che valga l’equivalenza
(x+ := e) ≡ (x := x + e)
e considerando come valori memorizzabili solo gli interi. Infine dimostrare
che effettivamente l’equivalenza vale.
S OLUZIONE . Il primo passo è capire come gli identificatori vengono mo-
dificati da questa aggiunta:
F I(x+ := e) = {x} ∪ F I(e)
DI(x+ := e) = ∅
Semantica statica
Consideriamo l’ambiente di tipi iniziale ∆ definito sull’insieme I di iden-
tificatori, in base al quale definiamo una nuova regola per la semantica
statica (nei seguenti esercizi tale considerazione verrà sottointesa)
∆ ⊢I e : int
, ∆(x) = intloc
∆ ⊢I x+ := e
5.2. ESERCIZI 57

Si noti che consideriamo corretto solo il caso in cui x sia di tipo intero e
denoti una locazione.
Semantica dinamica
Vediamo quali regole aggiungere alla semantica dinamica considerando un
ambiente ρ compatibile con l’ambiente di tipi ∆ generato dalle regole di
semantica statica (anche tale considerazione verrà d’ora in poi sottointesa)

ρ ⊢∆ he, σi →e he′ , σi
ρ ⊢∆ hx+ := e, σi →c hx+ := e′ , σi

ρ ⊢∆ he, σi →e hm, σi
ρ ⊢∆ hx+ := e, σi →c hx+ := m, σi

ρ ⊢∆ hx, σi →e hn, σi
, n = σ(l), l = ρ(x), m′ = m + n
ρ ⊢∆ hx+ := m, σi →c σ[m′ /l]

Si noti che è stato eliminato il caso in cui x sia dichiarata costante (n =


ρ(x)) perché un assegnamento è ben formato soltanto se la sua parte sini-
stra è dichiarata variabile.

L’esercizio chiede inoltre di dimostrare che effettivamente l’equivalenza


sia valida. In base alla definizione di equivalenza di comandi dobbiamo
vedere come i due comandi modificano la memoria a partire dalla stessa
memoria σ0 e dallo stesso ambiente ρ iniziali.

i. Consideriamo prima x+ := e. La valutazione di e non modifica σ0 ,


quindi possiamo scrivere

ρ ⊢∆ he, σ0 i →∗e hm, σ0 i


ρ ⊢∆ hx+ := e, σ0 i →c hx+ := m, σ0 i

A questo punto è sufficiente applicare l’ultima regola scritta sopra


per ottenere Exec(x+ := e, σ0 ) = σ0 [m′ /l] = σ1 .
58 CAPITOLO 5. COMANDI

ii. Consideriamo ora x := x + e. La regola


ρ ⊢∆ hx, σ0 i →e hn, σ0 i
, l = ρ(x), n = σ0 (l)
ρ ⊢∆ hx := x + e, σ0 i →c hx := n + e, σ0 i
rappresenta una valutazione parziale della parte destra dell’assegna-
mento. Essa consente di recuperare il valore contenuto nella loca-
zione l associata all’identificatore x nell’ambiente corrente ρ. Nota
che il valore inizialmente associato a x coincide nei due casi poiché
partiamo dallo stesso ambiente ρ e la stessa memoria σ0 . Per questo
stesso motivo la valutazione di e ottenuta mediante la regola
ρ ⊢∆ he, σ0 i →∗e hm, σ0 i
ρ ⊢∆ hx := n + e, σ0 i →c hx := n + m, σ0 i
genera lo stesso valore m del primo caso. L’ultimo passo di valuta-
zione dell’espressione x + e è descritto dalla regola
ρ ⊢∆ hm + n, σ0 i →e hm′ , σ0 i
, l = ρ(x), m′ = m + n

ρ ⊢∆ hx := m + n, σ0 i →c σ0 [m /l]
che somma i due interi ottenuti nei passi precedenti. Quindi la mo-
difica operata sulla memoria σ0 coincide con la modifica operata dal
nuovo comando x+ := e. Infatti, la memoria risultante è
Exec(x := x + e, σ0 ) = σ0 [m′ /l] = Exec(x+ := e, σ0 )
ed è cosı̀ dimostrata l’equivalenza.
Una soluzione alternativa e forse più immediata dell’esercizio sarebbe quel-
la di definire la semantica dinamica mediante l’assioma
ρ ⊢∆ hx+ := e, σi →c hx := x + e, σi.
L’inconveniente di questa scelta è che non viene conservato formalmente
il principio di composizionalità della semantica. Infatti il significato del
nuovo costrutto è definito in termini del normale assegnamento che non
compare nella sua sintassi. 2
5.2. ESERCIZI 59

Esercizio 5.3. Definire la semantica statica e dinamica del nuovo coman-


do x1 := (x2 := (· · · (xn := e) · · · )) in modo che valga l’equivalenza

(x1 := (x2 := (· · · (xn := e) · · · ))) ≡ (x1 := e′ )

dove e′ = (x2 := (· · · (xn := e) · · · )).


S OLUZIONE . Prima di tutto si suppone che il linguaggio su cui lavoriamo
contenga le modifiche fatte nell’Esercizio 3.4.
Vediamo allora come tale comando agisce sugli identificatori liberi e in
posizione di definizione
F I(x1 := (x2 := (· · · (xn := e) · · · ))) =
{x1 , x2 , · · · , xn } ∪ F I(e)
DI(x1 := (x2 := (· · · (xn := e) · · · ))) = ∅
Semantica statica
∆ ⊢I e : τ
, ∆(xi ) = τ loc, i = 1..n
∆ ⊢I (x1 := (x2 := (· · · (xn := e) · · · )))
Semantica dinamica
Si nota che considerando le espressioni introdotte nell’Esercizio 3.4 non è
necessario fare modifiche alla semantica dinamica in quanto il comando di-
venta un assegnamento di una espressione ad una variabile che è già imple-
mentato nel nostro linguaggio. Eventualmente l’unica cosa da evidenziare
è l’esecuzione di tale comando:
ρ ⊢∆ hxn := e, σi →∗e hk, σ[k/l]i
, l = ρ(xn )
ρ ⊢∆ hx1 := (· · · (xn := e) · · · ), σi →c
hx1 := (· · · (xn−1 := e) · · · ), σ[k/l]i
Nella regola appena definita invece di xn−1 := e potevamo mettere xn−1 :=
k; in tal modo si otteneva un comando diverso solo nel caso in cui ∃i >
1. xi ∈ Id(e) (Id(e) indica l’insieme degli identificatori contenuti in e)
poiché assegnando ad ogni passo agli identificatori la valutazione di e fatta
all’inizio non si tiene conto della successiva modifica eseguita su tale xi .
2
60 CAPITOLO 5. COMANDI

Esercizio 5.4. Definire la semantica statica e dinamica dell’assegnamento


parallelo (o collaterale)

x1 := e1 and x2 := e2 and · · · and xn := en

dove gli identificatori xi devono essere tutti differenti. L’esecuzione di que-


sto comando consiste nel valutare prima tutte le espressioni e poi eseguire
gli assegnamenti.
S OLUZIONE . Iniziamo subito col definire gli identificatori liberi e in posi-
zione di definizione:

F I(x1 := e1 and x2 := e2 and · · · and xn := en ) =


{x1 , x2 , . . . , xn } ∪ F I(e1 ) ∪ F I(e2 ) ∪ . . . ∪ F I(en )
DI(x1 := e1 and x2 := e2 and · · · and xn := en ) = ∅

Semantica statica
Dobbiamo verificare che il tipo associato a ciascun identificatore xi sia una
locazione che può memorizzare valori del tipo dell’espressione corrispon-
dente ei .

∆ ⊢I e1 : τ1 , ∆ ⊢I e2 : τ2 , . . . , ∆ ⊢I en : τn
, ∆(xi ) = τi loc, i = 1..n
∆ ⊢I x1 := e1 and · · · and xn := en

Semantica dinamica
Per eseguire il nuovo comando prima valutiamo tutte le espressioni ei

ρ ⊢∆ he1 , σi →∗e hm1 , σi


ρ ⊢∆ hx1 := e1 and · · · and xn := en , σi →c
hx1 := m1 and · · · and xn := en , σi

..
.
ρ ⊢∆ hen , σi →∗e hmn , σi
ρ ⊢∆ hx1 := m1 and · · · and xn := en , σi →c
hx1 := m1 and · · · and xn := mn , σi
5.2. ESERCIZI 61

e poi assegnamo i valori ottenuti ai corrispondenti identificatori ∀i ∈ [1, n]


ρ ⊢∆ hxi := mi , σi →c σ[mi /li ] 
σi = σ[mi /li ]
,
ρ ⊢∆ hxi := mi and · · · and xn := mn , σi →c li = ρ(vi )
hxi+1 := mi+1 and · · · and xn := mn , σi i
Si noti che, in assenza di specifiche più precise, nello scrivere le regole
abbiamo scelto di valutare le espressioni ed eseguire gli assegnamenti da
sinistra a destra. Di fatto questo può, in certe condizioni, essere differen-
te da una valutazione casuale degli assegnamenti descritta dalla regola di
inferenza
ρ ⊢∆ hei , σi →∗e hmi , σi
(∗)
ρ ⊢∆ hx1 := e1 and · · · and xi := ei and · · · and xn := en , σi →c
hx1 := e1 and · · · and xi := mi and · · · and xn := en , σi
In particolare tali regole non sono equivalenti nel caso in cui il nostro lin-
guaggio ammetta espressioni con side-effects ed ∃i, j. Mod(ei ) ∩Id(ej ) 6=
∅, dove Mod è l’estensione alle espressioni con effetti collaterali della fun-
zione definita nell’Esercizio 5.1. Consideriamo ad esempio la dichiarazio-
ne
x := (y := 1) and y := y + 1
assumendo che inizialmente l’ambiente ρ associ y ad una locazione ly con-
tenente 4. Con la valutazione da sinistra a destra otteniamo alla fine x = 1
e y = 2. Inizialmente valutiamo (y := 1) che ha come effetto collaterale
quello di assegnare il valore 1 alla locazione ly .
ρ ⊢∆ hy := 1, σ0 [4/ly ]i →c σ0 [1/ly ]
ρ ⊢∆ hx := (y := 1) and y := y + 1, σ0 i →c
hx := 1 and y := y + 1, σ0 [1/ly ]i
Il secondo passo consiste nel valutare l’espressione y + 1 nella memoria
modificata nel passo precedente e quindi con il valore 1 contenuto in ly .
ρ ⊢∆ hy, σ0 [1/ly ]i →e h1, σ0 [1/ly ]i
ρ ⊢∆ hy + 1, σ0 [1/ly ]i →e h2, σ0 [1/ly ]i
ρ ⊢∆ hx := 1 and y := y + 1, σ0 i →c hx := 1 and y := 2, σ0 [1/ly ]i
62 CAPITOLO 5. COMANDI

A questo punto eseguendo gli assegnamenti otteniamo che x varrà 1 e y 2.


Ora valutiamo da destra a sinistra e dimostriamo che alla fine avremo
x = 1 e y = 5. In questo caso valutiamo quindi y + 1 a partire da una
configurazione in cui ly contiene 4.
ρ ⊢∆ hy, σ0 [4/ly ]i →e h4, σ0 [4/ly ]i
ρ ⊢∆ hy + 1, σ0 [4/ly ]i →e h5, σ0 [4/ly ]i
ρ ⊢∆ hx := (y := 1) and y := y + 1, σ0 i →c
hx := (y := 1) and y := 5, σ0 [4/ly ]i
La valutazione di y := 1 coincide con il caso precedente.
ρ ⊢∆ hy := 1, σ0 [4/ly ]i →c σ0 [1/ly ]
ρ ⊢∆ hx := (y := 1) and y := 5, σ0 i →c hx := 1 and y := 5, σ0 [1/ly ]i
Come ci aspettavamo, effettivamente x vale ancora 1, ma y vale 5.
Si noti infine che, poiché la regola (*) consente entrambe le derivazioni,
essa introduce non determinismo nell’esecuzione dell’assegnamento. 2

Esercizio 5.5. Definire semantica statica e dinamica del nuovo comando


if e then c.
Mostrare poi che il nuovo comando può essere simulato mediante il co-
mando condizionale di I MP.
S OLUZIONE . Analogamente a ciò che è stato fatto negli esercizi precedenti
vediamo subito come variano gli insiemi FI e DI degli identificatori liberi
e in posizione di definizione:
F I(if e then c) = F I(e) ∪ F I(c)
DI(if e then c) = DI(c)
Semantica statica
Come per il comando condizionale di I MP dobbiamo solo controllare che il
tipo dell’espressione e sia un booleano e che il comando c sia ben formato.
∆ ⊢I e : bool, ∆ ⊢I c
∆ ⊢I if e then c
5.2. ESERCIZI 63

Semantica dinamica
Per eseguire il nuovo comando dobbiamo prima valutare l’espressione e e
poi passare ad eseguire c se otteniamo il valore tt
ρ ⊢∆ he, σi →∗e htt, σi
ρ ⊢∆ hif e then c, σi →c hc, σi
oppure terminare l’esecuzione del comando se e vale ff
ρ ⊢∆ he, σi →∗e hff, σi
ρ ⊢∆ hif e then c, σi →c σ
Per simulare questo costrutto con il costrutto condizionale di I MP è suffi-
ciente scrivere quello nuovo in funzione di quello vecchio e poi dimostrar-
ne l’equivalenza mediante Exec. Consideriamo

if e then c ≡ if e then c else nil

Per dimostrare l’equivalenza consideriamo una memoria iniziale σ0 . Ab-


biamo il caso in cui e vale tt e quello in cui e vale ff per ciascuno dei due
comandi.
i. Per eseguire if e then c, prima valutiamo e
ρ ⊢∆ he, σ0 i →∗e htt, σ0 i
ρ ⊢∆ hif e then c, σ0 i →c hc, σ0 i
e poi eseguiamo il comando c

ρ ⊢∆ hc, σ0 i →∗c σ1 .

Dunque se e è valutata tt, Exec(if e then c, σ0 ) = σ1 . Nel caso e


rappresenti ff la memoria finale coincide con quella iniziale, infatti
ρ ⊢∆ he, σ0 i →∗e hff, σ0 i
ρ ⊢∆ hif e then c, σ0 i →c σ0
Dunque Exec(if e then c, σ0 ) = σ0 .
64 CAPITOLO 5. COMANDI

ii. Per eseguire if e then c else nil prima valutiamo e. Se otteniamo il


valore tt selezioniamo il comando c
ρ ⊢∆ he, σ0 i →∗e htt, σ0 i
ρ ⊢∆ hif e then c else nil, σ0 i →c hc, σ0 i
e lo eseguiamo a partire dalla stessa memoria iniziale σ0 del caso
precedente ottenendo quindi anche la stessa memoria finale
ρ ⊢∆ hc, σ0 i →∗c σ1 .
Dunque se e vale tt, Exec(if e then c else nil, σ0 ) = σ1 . Se invece
e vale ff selezioniamo il comando nil
ρ ⊢∆ he, σ0 i →∗e hff, σ0 i
ρ ⊢∆ hif e then c else nil, σ0 i →c hnil, σ0 i
la cui esecuzione non produce modifiche sulla memoria
ρ ⊢∆ hnil, σ0 i →c σ0 .
Dunque se e rappresenta ff, Exec(if e then c else nil, σ0 ) = σ0 , e
quindi
∀e.Exec(if e then c, σ0 ) = Exec(if e then c else nil, σ0 ).

Esercizio 5.6. Dimostrare che l’equivalenza


if e then c0 else c1 ≡ if e then c0 ; if not e then c1
vale se e vale ff, oppure se Id(e) ∩ Mod(c0 ) = ∅, dove Mod è definita
come nell’Esercizio 5.1.
S OLUZIONE . Per dimostrare tale equivalenza dobbiamo vedere cosa suc-
cede applicando le regole di inferenza ai due diversi casi partendo dal-
la stessa memoria iniziale σ0 . Visto che il problema richiede di dimo-
strare come prima cosa che l’equivalenza vale se e vale ff, dividiamo la
dimostrazione in base al valore di e.
5.2. ESERCIZI 65

i. Consideriamo che il valore associato a e sia falso


ρ ⊢∆ he, σ0 i →∗e hff, σ0 i
ρ ⊢∆ hif e then c0 else c1 , σ0 i →c hc1 , σ0 i
questo per la definizione delle regole di inferenza per l’if. Perciò se
ρ ⊢∆ hc1 , σ0 i →∗c σ1 , si avrà Exec(if e then c0 else c1 , σ0 ) = σ1 .
Vediamo ora cosa accade nell’altro comando.
ρ ⊢∆ he, σ0 i →∗e hff, σ0 i
ρ ⊢∆ hif e then c0 , σ0 i →c σ0
ρ ⊢∆ hif e then c0 ; if not e then c1 , σ0 i →c hif not e then c1 , σ0 i
Poiché gli identificatori di e non sono stati modificati (abbiamo an-
cora la memoria σ0 ), avremo
ρ ⊢∆ he, σ0 i →∗e hff, σ0 i
ρ ⊢∆ hnot e, σ0 i →e htt, σ0 i
ρ ⊢∆ hif not e then c1 , σ0 i →c hc1 , σ0 i
Come nel caso precedente arriviamo alla configurazione hc1 , σ0 i e
quindi alla fine terminiamo nella stessa memoria. Possiamo dunque
affermare che se il valore booleano della guardia e è falso i comandi
sono equivalenti.
ii. Vediamo ora il caso in cui e vale tt e Id(e) ∩ Mod(c0 ) = ∅.
ρ ⊢∆ he, σ0 i →∗e htt, σ0 i
ρ ⊢∆ hif e then c0 else c1 , σ0 i →c hc0 , σ0 i
questo per la definizione delle regole di inferenza per l’if. Perciò se
ρ ⊢∆ hc0 , σ0 i →∗c σ1 , si avrà Exec(if e then c0 else c1 , σ0 ) = σ1 .
Vediamo adesso l’altro caso
ρ ⊢∆ he, σ0 i →∗e htt, σ0 i
ρ ⊢∆ hif e then c0 , σ0 i →c hc0 , σ0 i
ρ ⊢∆ hif e then c0 ; if not e then c1 , σ0 i →c
hc0 ; if not e then c1 , σ0 i
66 CAPITOLO 5. COMANDI

Per quello che è stato detto nel comando precedente, vale la seguente

ρ ⊢∆ hc0 , σ0 i →∗c σ1
ρ ⊢∆ hc0 ; if not e then c1 , σ0 i →c hif not e then c1 , σ1 i

A questo punto notiamo che il comando c0 non altera i valori asso-


ciati agli identificatori contenuti in e poiché Id(e) ∩ Mod(c0 ) = ∅.
Quindi
ρ ⊢∆ hnot e, σ1 i →∗e hff, σ1 i
ρ ⊢∆ hif not e then c1 , σ1 i →c σ1
che conclude la dimostrazione.

Esercizio 5.7. Definire semantica statica e dinamica del nuovo comando

if e1 then c1
elseif e2 then c2
..
.
elseif en then cn
else cn+1

inserito al posto del costrutto condizionale tradizionale. Mostrare poi che


il nuovo comando può essere definito in termini del comando condizionale
di I MP.
S OLUZIONE . Definiamo, come al solito, gli insiemi FI e DI per il nuovo
comando:

F I(if e1 then c1 elseif e2 then c2 . . . else cn+1 ) =


F I(c1 ) ∪ F I(c2 ) ∪ . . . ∪ F I(cn+1)∪
F I(e1 ) ∪ F I(e2 ) ∪ . . . ∪ F I(en )
DI(if e1 then c1 elseif e2 then c2 . . . else cn+1 ) =
DI(c1 ) ∪ DI(c2 ) ∪ . . . ∪ DI(cn+1)
5.2. ESERCIZI 67

Semantica statica
Dobbiamo verificare che tutte le guardie ei sono di tipo booleano e che i
comandi ci sono ben formati nell’ambiente statico in cui valuto il nuovo
comando
∆ ⊢I e1 : bool, . . . , ∆ ⊢I en : bool, ∆ ⊢I c1 , . . . , ∆ ⊢I cn+1
∆ ⊢I if e1 then c1 elseif e2 then c2 . . . else cn+1
Semantica dinamica
Iniziamo a valutare le guardie booleane partendo dalla prima. Se que-
sta è vera eseguiamo il comando corrispondente e terminiamo. Altrimenti
scartiamo il ramo then ed eseguiamo il sottocomando

if e2 then c2 elseif . . . else cn+1

Perciò le regole generica sono

ρ ⊢∆ hei , σi →∗e htt, σi


, i ∈ [1, n]
ρ ⊢∆ hif ei then ci . . . else cn+1 , σi →c hci , σi

ρ ⊢∆ hei , σi →∗e hff, σi


, i ∈ [1, n]
ρ ⊢∆ hif ei then ci . . . else cn+1 , σi →c
hif ei+1 then ci+1 . . . else cn+1 , σi
Data la semantica del costrutto possiamo dimostare che è simulabile dal
costrutto tradizionale semplicemente scrivendolo
if e1 then c1 else
if e2 then c2 else
..
.
if en then cn else cn+1
Come si può notare il cambiamento è solo sintattico dunque non è neces-
saria una esplicita dimostrazione di equivalenza. 2
68 CAPITOLO 5. COMANDI

Esercizio 5.8. Definire semantica statica e dinamica del nuovo comando

do e times c

con la semantica intuitiva di ripetere c fino a quando l’espressione e è


diversa da 0. Tale comando è detto stile for in quanto può eseguire c solo
un numero finito di volte determinato a priori.
S OLUZIONE . Definiamo prima di tutto gli insiemi FI e DI:

F I(do e times c) = F I(e) ∪ F I(c)


DI(do e times c) = DI(c)

Semantica statica
Per come è definita la semantica intuitiva, il comando è ben formato se
l’espressione e è di tipo intero e se il comando c è ben formato
∆ ⊢I e : int, ∆ ⊢I c
∆ ⊢I do e times c
Semantica dinamica
Per eseguire tale comando valutiamo, per prima cosa, l’espressione deter-
minando cosı̀ il valore numerico m ad essa corrispondente. A questo punto
eseguiamo m volte il comando c decrementando, ad ogni esecuzione di c
il valore m. Poiché la semantica statica non ci assicura che il valore di
e sia positivo, dobbiamo inserire nella semantica dinamica un controllo a
tempo di esecuzione (m > 0). Infatti, se m fosse negativo il comando non
terminerebbe mai.
ρ ⊢∆ he, σi →e he′ , σi
ρ ⊢∆ hdo e times c, σi →c hdo e′ times c, σi

ρ ⊢∆ hdo m times c, σi →c hc; do m − 1 times c, σi, m > 0

ρ ⊢∆ hdo 0 times c, σi →c σ
5.2. ESERCIZI 69

Notiamo che si poteva scegliere di valutare l’epressione ad ogni passo ese-


guendo il decremento, non su m, ma su e. Questa scelta però è equi-
valente alla precedente solo nel caso in cui sia verificata la condizione
Id(e) ∩ Mod(c) = ∅, dove Mod(c) è la funzione che restituisce gli identi-
ficatori che possono essere modificati da c definita nell’Esercizio 5.1 estesa
opportunamente al presente comando. Infatti, in caso contrario, la termi-
nazione del ciclo non sarebbe garantita perchè l’esecuzione di c potrebbe
alterare la condizione di terminazione non facendo mai arrivare il valore di
e a 0. 2

Esercizio 5.9. Definire semantica statica e dinamica del nuovo comando


di iterazione

loop
c1
when e1 do c′1 exit
c2
..
.
when en do c′n exit
cn+1
repeat

con la semantica intuitiva di eseguire ripetutamente c1 , c2 , . . . , cn+1 fino a


quando non sia verificata una delle condizioni ei di uscita. A questo punto
si esegue il corrispondente comando e si termina.
S OLUZIONE . Per semplicità identifichiamo l’intero nuovo comando con
c1 . Definiamo allora gli insiemi FI e DI:
n
[
F I(c1 ) = F I(c1 ) ∪ ((F I(ci+1 ) ∪ F I(c′i ) ∪ F I(ei )) \ DI(ci))
i=1
n
[
DI(c1 ) = (DI(ci) ∪ DI(c′i)) ∪ DI(cn+1 )
i=1
70 CAPITOLO 5. COMANDI

Semantica statica
Perché il comando sia ben formato tutte le espressioni devono essere boo-
leane e i comandi devono essere ben formati
∆ ⊢I ∀i ∈ [1, n] . ei : bool, ∆ ⊢I ∀i ∈ [1, n] . c′i, ∆ ⊢I ∀i ∈ [1, n + 1] . ci
∆ ⊢ I c1

Semantica dinamica
L’esecuzione del comando di loop consiste nell’eseguire, ad ogni passo, un
comando ci , valutare una condizione ei e, se questa è falsa, andare avanti,
altrimenti eseguire il comando c′i e uscire dal ciclo. I comandi ci devono es-
sere eseguiti secondo l’ordine sequenziale in cui compaiono nel corpo del
loop. Quindi per permettere di eseguire il comando giusto e per permettere
di poter ripetere l’intero loop se nessuna condizione risulta vera dobbiamo
portarci dietro, nelle regole, rispettivamente il comando originale, privato
dei comandi già eseguiti e delle condizioni già controllate (ci ), e l’intero
comando originale (c1 ). Utilizzeremo la notazione

ci = ci ; when ei do c′i exit ci+1 · · · cn+1 , i ∈ [1, n]

assumendo cn+1 = cn+1 . Quindi interpretiamo i componenti del loop come


una composizione sequenziale di comandi.

ρ ⊢∆ hc1 , σi →c hc1 ; when e1 then c′1 exit; c2 ; c1 , σi

ρ ⊢∆ hci , σi →c hci ; when ei then c′i exit; ci+1 , σi


Abbiamo introdotto due regole per distinguere il primo passo dai succes-
sivi per evitare di accumulare copie di c1 in coda. Per poter specificare
la semantica del loop ci rimane quindi da definire la semantica di when.
Questa ci dice che se la condizione ei è vera, eseguiamo c′i nella memoria
corrente e scartiamo la continuazione del loop (ci+1 ) e la copia dell’intero
comando (c1 ) delimitata da loop e repeat che abbiamo introdotto con la
prima regola.

ρ ⊢∆ hei , σi →∗e htt, σi


ρ ⊢∆ hwhen ei do c′i exit; ci+1 ; c1 , σi →c hc′i , σi
5.2. ESERCIZI 71

Nel caso in cui la condizione ei sia falsa, scartiamo il comando corrispon-


dente e procediamo con la continuazione ci+1 del loop.

ρ ⊢∆ hei , σi →∗e hff, σi


ρ ⊢∆ hwhen ei do c′i exit; ci+1 ; c1 , σi →c hci+1 ; c1 , σi
2

Esercizio 5.10. Si consideri la possibilità di dichiarare identificatori senza


allocarli fino a quando non viene fatta una richiesta esplicita. Si dia allora
semantica statica e dinamica al nuovo comando

allocate(x)

in modo che il seguente frammento di programma

var x : int;
begin
x := 1; allocate(x)
end

dia errore a tempo di esecuzione.


S OLUZIONE . Definiamo gli identificatori liberi e in posizione di defini-
zione del nuovo comando. Poiché non possiamo allocare un identificatore
prima di averlo dichiarato, abbiamo

F I(allocate(x)) = {x} DI(allocate(x)) = ∅

Notiamo che allocando dinamicamente gli identificatori bisogna trovare


un modo per distinguere quegli identificatori che sono stati dichiarati, ma
non ancora allocati. Aggiungiamo per questo un simbolo alle locazioni da
associare agli identificatori quando vengono dichiarati in modo da identifi-
care a tempo di esecuzione se un identificatore è stato allocato oppure no.
Il nuovo insieme di locazioni è

Loc′ = Loc ∪ {l⊥ }.


72 CAPITOLO 5. COMANDI

Per poter evidenziare la situazione di errore nel caso in cui venga utilizzata
una variabile non ancora allocata definiamo σ(l⊥ ) =⊥.
Semantica statica
Dal punto di vista statico il nuovo comando è corretto se l’identificatore x
che si vuole allocare è stato precedentemente dichiarato e quindi compare
nel dominio di ∆.
∆ ⊢I allocate(x), x ∈ I

Semantica dinamica
Per la semantica dinamica del nuovo comando, dobbiamo modificare an-
che la semantica dinamica dell’assegnamento e della dichiarazione di va-
riabile.


 ρ(x) = l⊥



ρ ⊢∆ hallocate(x), σi →c h[x = l]; nil, σi, ∆(x) = τ loc




l = Newτ (L), σ : L

Si noti che l’unica modifica che il nuovo comando deve fare è quella di
associare all’identificatore x una nuova locazione, quindi deve modificare
ρ con [x = l]. La condizione ρ(x) = l⊥ ci assicura che allochiamo un
identificatore solo una volta e ∆(x) = τ loc è necessaria per scegliere una
nuova locazione del tipo dichiarato per x. Notiamo inoltre che è possibile
allocare solo identificatori dichiarati di tipo var. Inoltre si noti che tecnica-
mente stiamo eseguendo un comando, dunque non possiamo farlo transire
in una configurazione del tipo hρ0 , σi perché le configurazioni dei comandi
sono del tipo hc, σi o σ. Dobbiamo allora fare in modo che il nuovo co-
mando transisca in un altro comando. Avendo a disposizione un ambiente
possiamo pensare di renderlo un blocco sequenzializzandolo col comando
nil che non esegue alcuna modifica.
Vediamo adesso come modifichiamo la semantica dell’assegnamento

ρ ⊢∆ he, σi →∗e hk, σi


, l = ρ(x) 6= l⊥ (5.1)
ρ ⊢∆ hx := e, σi →c σ[k/l]
5.2. ESERCIZI 73

Dunque dinamicamente controlliamo che l’assegnamento non venga fatto


su una variabile dichiarata ma ancora non allocata.

Consideriamo infine la dichiarazione di variabile. Associamo ad x la loca-


zione speciale l⊥ e scriviamo ⊥ in σ in corrispondenza di l⊥ .
ρ ⊢∆ hvar x : τ, σi →d h[x = l⊥ ], σ[l⊥ =⊥]i
Per completezza possiamo scrivere le regole che generano l’errore (poi da
propagare a tutti i contesti)
ρ ⊢∆ he, σi →∗e hk, σi
, ρ(x) = l⊥
ρ ⊢∆ hx := e, σi →c ERRORE
ρ ⊢∆ hx, σi →e ERRORE, σ(ρ(x)) =⊥
Si noti che è proprio la prima di queste due regole a causare un errore
dinamico nel frammento fornito dal testo. Infatti, applicando le regole
definite nell’Esercizio 4.2 a partire da un ambiente ρ0 e una memoria σ0 si
ha che
ρ0 ⊢∆ hvar x : int, σ0 i →d h[x = l⊥ ], σ0 [l⊥ = ⊥]i
Adesso dobbiamo valutare il corpo del frammento nell’ambiente modifi-
cato dalla dichiarazione fatta. Siano ρ1 = ρ0 [x = l⊥ ] e σ1 = σ0 [l⊥ = ⊥]
l’ambiente e la memoria in cui dobbiamo valutare l’assegnamento; allora
ρ(x) = l⊥ e dunque la regola (5.1) non è applicabile, mentre lo è la prima
tra quelle che generano errore e quindi
ρ1 ⊢∆ hx := 1, σ1 i →c ERRORE , ρ1 (x) = l⊥
2

Esercizio 5.11. Aggiungere ad I MP la composizione collaterale di coman-


di con sintassi
ckc
e semantica intuitiva: “eseguire i due sottocomandi indipendentemente
ed in un qualunque ordine”. Definire semantica statica e dinamica e di-
mostrare che c0 k c1 è equivalente a c1 k c0 . Determinare sotto quali
condizioni c1 k c0 è equivalente a c1 ; c0 .
74 CAPITOLO 5. COMANDI

S OLUZIONE . Prima di tutto bisogna determinare quali sono gli identifica-


tori liberi e quelli in posizione di definizione estendendo le definizioni di
DI ed FI per il nuovo comando:

F I(c0 k c1 ) = F I(c0 ) ∪ F I(c1 )


DI(c0 k c1 ) = DI(c0 ) ∪ DI(c1 )

Semantica statica
A questo punto dobbiamo definire le regole di semantica statica che deter-
minano quando il comando è ben formato; poiché i due comandi sono ese-
guiti in modo indipendente l’ambiente di tipi in cui si controllano entrambi
i comandi è lo stesso
∆ ⊢ I c0 , ∆ ⊢ I c1
∆ ⊢ I c0 k c1
Si noti che le eventuali dichiarazioni presenti in un comando non influen-
zano gli identificatori liberi dell’altro poiché c0 e c1 devono essere indipen-
denti. Non ci sono altri aspetti statici che vengono influenzati.
Semantica dinamica
Le prime due regole esplicitano il fatto che non esiste un ordine prefissa-
to per l’esecuzione dei due comandi. Le altre due regole ci dicono che
una volta terminata l’esecuzione di un comando si prosegue con il residuo
dell’altro.
ρ ⊢∆ hc0 , σi →c hc′0 , σ ′ i
1.
ρ ⊢∆ hc0 k c1 , σi →c hc′0 k c1 , σ ′ i
ρ ⊢∆ hc1 , σi →c hc′1 , σ ′ i
2.
ρ ⊢∆ hc0 k c1 , σi →c hc0 k c′1 , σ ′ i
ρ ⊢∆ hc0 , σi →c σ ′
3.
ρ ⊢∆ hc0 k c1 , σi →c hc1 , σ ′ i
ρ ⊢∆ hc1 , σi →c σ ′
4.
ρ ⊢∆ hc0 k c1 , σi →c hc0 , σ ′ i
Per dimostrare che c0 k c1 è equivalente a c1 k c0 dobbiamo dimostrare
che Exec(c0 k c1 , σ0 ) = Exec(c1 k c0 , σ0 ).
5.2. ESERCIZI 75

In seguito utilizzeremo la scrittura Exec(c, σ) =k σ ′ , dove il k denota il


numero di transizioni effettuate, intendendo dire che hc, σi →kc hc′ , σ ′ i op-
pure hc, σi →kc σ ′ .
Tale dimostrazione può essere fatta per induzione sul numero k di passi
che vengono eseguiti. La dimostrazione si basa sul fatto che per ogni tran-
sizione che possiamo fare su c0 k c1 riusciamo a farne una su c1 k c0 che
esegue le stesse modifiche e viceversa (infatti le regole sono simmetriche).

BASE k = 1
Se eseguiamo prima c0 ed un passo di esecuzione non conduce ad una con-
figurazione terminale, applichiamo la regola (1.) oppure (2.) a seconda che
c0 sia alla sinistra o alla destra di k

ρ ⊢∆ hc0 , σ0 i →c hc′0 , σ1 i
(1.)
ρ ⊢∆ hc0 k c1 , σ0 i →c hc0 k c1 , σ1 i

ρ ⊢∆ hc0 , σ0 i →c hc′0 , σ1 i
(2.)
ρ ⊢∆ hc1 k c0 , σ0 i →c hc1 k c′0 , σ1 i
Se invece il passo di esecuzione di c0 produce una memoria finale σ1 , le
due regole che possiamo applicare sono (3.) o (4.).

ρ ⊢∆ hc0 , σ0 i →c σ1
(3.)
ρ ⊢∆ hc0 k c1 , σi →c hc1 , σ1 i

ρ ⊢∆ hc0 , σ0 i →c σ1
(4.)
ρ ⊢∆ hc1 k c0 , σ0 i →c hc1 , σ1 i
Il numero (i.) alla sinistra indica la regola usata. Analogamente, se ese-
guiamo prima c1 abbiamo

ρ ⊢∆ hc1 , σ0 i →c hc′1 , σ1 i
(2.)
ρ ⊢∆ hc0 k c1 , σ0 i →c hc0 k c′1 , σ1 i
76 CAPITOLO 5. COMANDI

ρ ⊢∆ hc1 , σ0 i →c hc′1 , σ1 i
(1.)
ρ ⊢∆ hc1 k c0 , σ0 i →c hc′1 k c0 , σ1 i
oppure
ρ ⊢∆ hc1 , σ0 i →c σ1
(4.)
ρ ⊢∆ hc0 k c1 , σ0 i →c hc0 , σ1 i

ρ ⊢∆ hc1 , σ0 i →c σ1
(3.)
ρ ⊢∆ hc1 k c0 , σ0 i →c hc0 , σ1 i
Quindi dopo un passo di esecuzione

Exec(c0 k c1 , σ0 ) =1 Exec(c1 k c0 , σ0 ) =1 σ1 .

Inoltre, per come sono definite le regole, c′0 , c′1 e σ1 ottenuti dopo un passo
di computazione sono uguali sia se si parte da c0 k c1 sia se si parte da
c1 k c0 .

PASSO INDUTTIVO
Supponiamo che dopo k passi

Exec(c0 k c1 , σ0 ) =k Exec(c1 k c0 , σ0 ) =k σk

e dobbiamo dimostrare che tale relazione vale ancora dopo k + 1 passi.


Se eseguiamo prima c0 e questo non conduce direttamente ad una configu-
razione finale, abbiamo

ρ ⊢∆ hc′0 , σk i →c hc′′0 , σk+1 i


(1.)
ρ ⊢∆ hc′0 k c′1 , σk i →c hc′′0 k c′1 , σk+1 i

ρ ⊢∆ hc′0 , σk i →c hc′′0 , σk+1 i


(2.)
ρ ⊢∆ hc′1 k c′0 , σ0 i →c hc′1 k c′′0 , σk+1 i
5.2. ESERCIZI 77

Se eseguiamo prima c1 invece possiamo ottenere


ρ ⊢∆ hc′1 , σk i →c hc′′1 , σk+1 i
(2.)
ρ ⊢∆ hc′0 k c′1 , σk i →c hc′0 k c′′1 , σk+1 i

ρ ⊢∆ hc′1 , σk i →c hc′′1 , σk+1 i


(1.)
ρ ⊢∆ hc′1 k c′0 , σk i →c hc′′1 k c′0 , σk+1 i

Analogo nei casi in cui i comandi vadano in una memoria.


In queste regole abbiamo considerato σk , c′0 e c′1 rispettivamente la memo-
ria, c0 e c1 dopo k passi di computazione; si noti che c′0 , c′1 e σk ottenuti
lavorando a partire da c0 k c1 sono uguali a quelli ottenuti a partire dall’al-
tro comando per ipotesi induttiva. Allora Exec(c′0 k c′1 , σk ) =1 Exec(c′1 k
c′0 , σk ) =1 σk+1 dopo un passo di computazione, perciò si può concludere
che

Exec(c0 k c1 , σ0 ) =k+1 Exec(c1 k c0 , σ0 ) =k+1 σk+1

Poiché le transizioni che eseguono c0 k c1 e c1 k c0 sono le stesse, la


dimostrazione è conclusa. Infine, per rispondere all’ultima richesta dell’e-
sercizio, bisogna capire in quale condizione l’esecuzione indipendente dei
comandi c0 e c1 è equivalente all’esecuzione sequenziale, ad esempio di
c1 prima di c0 . Se i due comandi lavorano sulle stesse variabili l’eseguire
prima uno o prima l’altro non può essere indifferente, mentre se lavorano
su variabili distinte i due comandi sono totalmente indipendenti. Allora la
condizione da imporre dovrà garantire che gli insiemi degli identificatori
modificati da un comando ed utilizzati dall’altro siano disgiunti. Quindi

Mod(c0 ) ∩ Id(c1 ) = ∅ ∧ Mod(c1 ) ∩ Id(c0 ) = ∅

dove Id(c) indica l’insieme di identificatori utilizzati dal comando c e


Mod(c) quelli potenzialmente modificabili da c (vedi Esercizio 5.1). 2

Esercizio 5.12. Si consideri il linguaggio I MP modificato nel seguente


modo:
78 CAPITOLO 5. COMANDI

• il tipo int coincide con i naturali;

• ogni comando non contiene sottoespressioni del tipo e − e′ ;

• come comando iterativo non si ha il while ma il comando definito


nell’Esercizio 5.8.

Dimostrare che qualsiasi sequenza di comandi termina sempre.


S OLUZIONE . Questa dimostrazione va fatta per induzione strutturale sulle
espressioni e sui comandi del linguaggio la cui sintassi è riportata sotto per
comodità.

c ::= nil | id := e | c; c | if e then c else c | do e times c


e ::= k | id | e bop e | uop e

Poiché la definizione dei comandi si fonda sulla definizione delle espres-


sioni dobbiamo prima dimostrare induttivamente che la valutazione delle
espressioni porta sempre in configurazioni terminali. Supponiamo inoltre
che tutti i costrutti su cui facciamo la dimostrazione siano sintatticamen-
te corretti, cioè supponiamo che la semantica statica non abbia generato
errori.

i. Dimostriamo per induzione sulla definizione delle espressioni che la


loro valutazione termina sempre in configurazioni del tipo hk, σi.

BASE
Per le costanti non dobbiamo fare nulla poiché hk, σi è terminale.
Rimangono da considerare gli identificatori.

ρ ⊢∆ hid, σi →e hk, σi,

k = σ(ρ(id)) se ρ(id) ∈ Loc o k = ρ(id) se ρ(id) ∈ EV al

PASSO INDUTTIVO
Supponiamo, per ipotesi induttiva, che la valutazione di e1 ed e2
conduca sempre ad una configurazione terminale e dunque possiamo
5.2. ESERCIZI 79

sempre assegnare loro un valore. Allora le premesse delle seguenti


regole di inferenza valgono per ipotesi induttiva:

ρ ⊢∆ he1 , σi →∗e hm1 , σi


ρ ⊢∆ he1 bop e2 , σi →e hk1 bop e2 , σi

ρ ⊢∆ he2 , σi →∗e hk2 , σi


ρ ⊢∆ hk1 bop k2 , σi →e hk1 bop k2 , σi

ρ ⊢∆ hk1 bop k2 , σi →e hk ′ , σi, k ′ = k1 bop k2


Si noti che questo assioma è sempre applicabile perché le bop de-
finite nel nostro linguaggio sono totali e non abbiamo la sottrazione
che potrebbe generare valori negativi non ammessi (seconda ipotesi).
Dunque raggiungiamo sempre una configurazione terminale. Natu-
ralmente, però, se avessimo anche la divisione tra le bop, questo sa-
rebbe vero solo se m2 6= 0 che non può essere deciso staticamente.
Vediamo ora cosa succede con l’operatore unario uop.

ρ ⊢∆ he1 , σi →∗e ht1 , σi


, t′ = uop t1

ρ ⊢∆ huop e1 , σi →e ht , σi

Si noti che anche la valutazione di questa espressione termina. Pos-


siamo allora concludere che ad ogni espressione riusciamo ad asso-
ciare un valore intero o booleano e cioè raggiungiamo una configu-
razione terminale.

ii. Presupponendo valido ciò che abbiamo appena dimostrato per le


espressioni, dimostriamo per induzione sulla definizione dei coman-
di che l’esecuzione di ogni comando termina in configurazioni ter-
minali del tipo σ.

BASE
80 CAPITOLO 5. COMANDI

Gli unici comandi che costituiscono la base sono nil e l’assegna-


mento che non contengono sottocomandi

ρ ⊢∆ hnil, σi →c σ

Dunque tale comando termina. Per l’assegnamento abbiamo

ρ ⊢∆ he, σi →∗e hk, σi


, l = ρ(x)
ρ ⊢∆ hx := e, σi →c σ[l = k]

Anche questo comando porta ad una configurazione terminale.

PASSO INDUTTIVO
Supponiamo per ipotesi induttiva che c1 e c2 siano due comandi che
terminano. Quindi le premesse delle seguenti regole di inferenza
valgono o per ipotesi induttiva o per la dimostrazione precedente.
Consideriamo prima la sequenzializzazione

ρ ⊢∆ hc1 , σi →∗c σ ′
ρ ⊢∆ hc1 ; c2 , σi →c hc2 , σ ′ i

ρ ⊢∆ hc2 , σ ′ i →∗c σ ′′
Dunque, poiché questo ultimo assioma vale per ipotesi induttiva, il
comando c1 ; c2 termina sempre. Vediamo adesso il costrutto condi-
zionale
ρ ⊢∆ he, σi →∗e htt, σi
ρ ⊢∆ hif e then c1 else c2 , σi →c hc1 , σi
ρ ⊢∆ hc1 , σi →∗c σ ′
Anche in questo caso l’esecuzione di c1 termina per ipotesi induttiva.

ρ ⊢∆ he, σi →∗e hff, σi


ρ ⊢∆ hif e then c1 else c2 , σi →c hc2 , σi

ρ ⊢∆ hc2 , σi →∗c σ ′′
5.2. ESERCIZI 81

Allora, visto che anche questo comando termina, ci resta da dimo-


strare che termina anche il comando iterativo
ρ ⊢∆ he, σi →∗e hm, σi
ρ ⊢∆ hdo e times c1 , σi →c hc1 ; do m − 1 times c1 , σi
ρ ⊢∆ hc1 , σi →∗c σ ′
ρ ⊢∆ hc1 ; do m − 1 times c1 , σi →∗c hdo m − 1 times c1 , σ ′ i
Il numero m ∈ N poiché non abbiamo né numeri negativi né sottoe-
spressioni del tipo e − e′ nei comandi. Poiché l’insieme dei nume-
ri naturali è un insieme ben fondato, raggiungiamo sicuramente la
seguente configurazione dopo m esecuzioni di c1
ρ ⊢∆ hdo 0 times c1 , σi →c σ
Visto che anche questo comando termina, la dimostrazione è conclu-
sa.
2

Esercizio 5.13. Dimostrare che


ρ ⊢∆ hc, σi →c hc′ , σ ′ i ⇒
∀x ∈ Dom(ρ) \ Id(c). σ(l) = σ ′ (l), ρ(x) = l
dove Id(c) indica l’insieme degli identificatori che occorrono in c.
S OLUZIONE . Dimostriamo l’implicazione per induzione strutturale sulla
definizione dei comandi del nostro linguaggio.

BASE
Il comando nil non modifica la memoria
ρ ⊢∆ hnil, σi →c σ
e quindi l’implicazione vale. Vediamo ora l’assegnamento
ρ ⊢∆ he, σi →∗e hk, σi
, l0 = ρ(x0 )
ρ ⊢∆ hx0 := e, σi →c σ[l0 = k]
82 CAPITOLO 5. COMANDI

Poiché x0 è un identificatore del comando che stiamo eseguendo e poiché


per ipotesi x 6∈ Id(c) allora sicuramente x0 6= x. Inoltre per definizione
σ[l0 = k] = σ ′ è tale che (sapendo che l0 è la locazione associata a x0 )
∀x 6= x0 . σ(x) = σ ′ (x).

PASSO INDUTTIVO
Supponiamo a questo punto di prendere un comando c i cui sottocomandi
possono essere c1 e/o c2 . Per ipotesi abbiamo che x 6∈ Id(c) dunque, poi-
ché l’insieme degli identificatori di un comando contiene gli identificatori
dei suoi sottocomandi, sicuramente x 6∈ Id(c1 ) ∪ Id(c2 ) e quindi possiamo
applicare caso per caso l’ipotesi induttiva.
Vediamo come funziona la dimostrazione per il comando c = c1 ; c2 dove
supponiamo che, nell’ambiente ρ, hc1 , σi →∗c σ1 e hc2 , σ1 i →∗c σ ′ . Per
ipotesi induttiva si ha che σ1 (x) = σ(x) e che σ1 (x) = σ ′ (x), perciò

ρ ⊢∆ hc1 , σi →∗c σ1
ρ ⊢ ∆hc1 ; c2 , σi →∗c hc2 , σ1 i

ρ ⊢∆ hc2 , σ1 i →∗c σ ′
da cui

ρ ⊢∆ hc1 ; c2 , σi →∗c σ ′ .

Quindi sappiamo che, per le ipotesi induttive e per la proprietà transitiva


dell’uguaglianza, σ(x) = σ ′ (x).
Vediamo adesso cosa succede per il comando condizionale. Supponiamo
che, nell’ambiente ρ, hc1 , σi →∗c σ1 e hc2 , σi →∗c σ2 ; allora le ipotesi
induttive sono σ1 (x) = σ(x) = σ2 (x)

ρ ⊢∆ he, σi →∗e htt, σi


ρ ⊢∆ hif e then c1 else c2 , σi →c hc1 , σi

Per ipotesi induttiva sappiamo che tale comando termina in una memoria
σ1 che non altera il valore di x. Cioè σ(x) = σ1 (x). Vediamo l’altro caso
5.2. ESERCIZI 83

dell’if
ρ ⊢∆ he, σi →∗e hff, σi
ρ ⊢∆ hif e then c1 else c2 , σi →c hc2 , σi
Anche qui per ipotesi induttiva sappiamo che il comando c2 termina in una
memoria σ2 che non altera il valore di x, cioè σ(x) = σ2 (x).
Vediamo infine il caso del while. Supponiamo che, in ρ, hc1 , σi →∗c σ1 , e
dunque per ipotesi induttiva vale σ(x) = σ1 (x). Poiché nel comando ite-
rativo si esegue c1 ripetutamente le ipotesi possono non essere conservate.
Dunque per completare la dimostrazione bisogna dimostrare, per induzio-
ne sul numero di cicli eseguiti dal while, che, se dopo n passi la memoria
è σn allora vale σn (x) = σ(x). Questo conclude l’induzione strutturale.
BASE Vediamo cosa succede con un passo di esecuzione:
ρ ⊢∆ he, σi →∗e hff, σi
ρ ⊢∆ hwhile e do c1 , σi →c σ
La memoria non viene modificata e quindi le ipotesi fatte rimangono
valide. Vediamo ora l’altro caso
ρ ⊢∆ he, σi →∗e htt, σi
(5.2)
ρ ⊢∆ hwhile e do c1 , σi →∗c hwhile e do c1 , σ1 i
Per ipotesi σ(x) = σ1 (x) e quindi l’enunciato da dimostrare vale
nel caso base. Si nota, inoltre, che la regola di inferenza corretta
per while e do c1 è differente da quella qui riportata per il fatto
che prima di eseguire nuovamente il while si esegue c1 . Abbiamo
condensato questi passi in un’unica regola per semplicità e maggiore
chiarezza.
PASSO INDUTTIVO Supponiamo che dopo n applicazioni della regola (5.2)
la memoria risultante, a partire dalla memoria iniziale σ, sia σn . Per
ipotesi induttiva (sui cicli) assumiamo σ(x) = σn (x). Vediamo,
dunque, cosa succede dopo n + 1 passi del while
ρ ⊢∆ hc1 , σn i →∗c σn+1
ρ ⊢∆ hwhile e do c1 , σn i →c hwhile e do c1 , σn+1 i
84 CAPITOLO 5. COMANDI

Per ipotesi x 6∈ Id(while e do c1 ) e quindix 6∈ Id(c1 ). Per ipotesi


induttiva strutturale vale che σn+1 (x) = σn (x), mentre per ipotesi
induttiva sui cicli σn (x) = σ(x) e dunque per transitività dell’ugua-
glianza si ha σn+1 (x) = σ(x).

Esercizio 5.14. Aggiungere ad I MP il comando

c ::= d init c end

il cui significato è quello di eseguire i comandi c di inizializzazione di d


prima dei comandi del blocco in cui d compare.
S OLUZIONE . Questo esercizio può essere velocemente risolto se si capi-
sce bene cosa fa esattamente tale comando; la sua semantica intuitiva è
costituita dai seguenti passi:

1. elaborare d;

2. eseguire c;

3. proseguire oltre.

Questa è esattamente la semantica intuitiva del comando d; c quindi pos-


siamo dire che se I MP ha semantica S EM il nostro nuovo linguaggio ha
semantica S EM/≡ dove (d init c end, c; d) ∈≡. In tal modo la risoluzione
dell’esercizio sarebbe finita; comunque per completezza diamo le regole
di inferenza per il comando.

Definiamo gli identificatori liberi e in posizione di definizione:

F I(d init c end) = F I(c) \ DI(d)


DI(d init c end) = DI(d)

Semantica statica
Dobbiamo controllare che il nuovo comando sia ben formato. Questo av-
viene se la dichiarazione d genera un ambiente di tipi ∆0 corretto ed il
5.2. ESERCIZI 85

comando c è ben formato tenendo conto anche dei legami introdotti da d.

∆ ⊢I d : ∆0 , ∆[∆0 ] ⊢I∪I0 c
, ∆0 : I0
∆ ⊢I d init c end
Semantica dinamica
Per descrivere il comportamento dinamico del nuovo costrutto dobbiamo
definire delle regole che ricalcano quelle del blocco. Questo garantisce
anche che l’equivalenza d init c end ≡ d;c sia verificata.

ρ ⊢∆ hd, σi →d hd′ , σ ′ i
ρ ⊢∆ hd init c end, σi →c hd′ init c end, σ ′ i

ρ[ρ0 ] ⊢∆[∆0 ] hc, σi →c hc′ , σ ′ i


, ρ0 : ∆0
ρ ⊢∆ hρ0 init c end, σi →c hρ0 init c′ end, σ ′ i

ρ[ρ0 ] ⊢∆[∆0 ] hc, σi →c σ ′


, ρ0 : ∆0
ρ ⊢∆ hρ0 init c end, σi →c σ ′
2

Esercizio 5.15. Si modifichi la sintassi di I MP aggiungendo alla categoria


sintattica delle espressioni il costrutto

e ::= begin c result e

con il significato intuitivo di eseguire c e poi valutare e.


S OLUZIONE . Per quel che riguarda gli identificatori liberi dobbiamo ag-
giungere (alla definizione di F I)

F I(begin c result e) = F I(c) ∪ (F I(e) \ DI(c))

Diverso è il discorso per gli identificatori in posizione di definizione. In-


fatti le nuove espressioni contengono i comandi che possono modificare il
86 CAPITOLO 5. COMANDI

valore degli identificatori con assegnamenti, e possono contenere dichia-


razioni che definiscono identificatori. Quindi dobbiamo aggiungere al no-
stro linguaggio la definizione dell’insieme di identificatori definiti (DI) da
un’espressione:
DI(m) = DI(t) = ∅
DI(x) = ∅
DI(begin c result e) = DI(c) ∪ DI(e)
DI(e0 bop e1 ) = DI(e0 ) ∪ DI(e1 )
DI(not e) = DI(e)
A questo punto dobbiamo definire la semantica statica e dinamica del
nuovo costrutto.
Semantica statica
Consideriamo l’ambiente di tipi iniziale ∆ definito sull’insieme I di iden-
tificatori, in base al quale determiniamo il tipo della nuova espressione. La
regola più naturale per la semantica statica è
∆ ⊢I c, ∆ ⊢I e : τ
∆ ⊢I begin c result e : τ
cioè la nuova espressione è ben formata se lo sono i suoi elementi com-
ponenti. Questa regola purtroppo non è corretta. Consideriamo infatti
l’espressione
tt or begin var z : int = 4; nil end (z = 4)
che è corretta poiché l’identificatore z è dichiarato nel corpo del begin.
Se applichiamo la regola data quando andiamo a valutare (z = 4) non
abbiamo nessun legame per z in ∆ e quindi otteniamo un errore. Infat-
ti, dovremmo valutare l’espressione nell’ambiente ∆ esteso con i legami
generati dalle eventuali dichiarazioni presenti in c. In effetti, questo è ciò
che avevamo in mente quando abbiamo esteso F I e definito DI. Per que-
sto motivo modifichiamo le regole della semantica statica dei comandi in
modo che questi restituiscano l’ambiente di tipi generato dalle eventuali di-
chiarazioni che contengono. Le nuove regole per i comandi sono riportate
in Tab. 5.3.
5.2. ESERCIZI 87

∆ ⊢I e : τ
∆ ⊢I nil : ∅ , ∆(x) = τ loc
∆ ⊢I x := e : ∅

∆ ⊢I c0 : ∆0 , ∆ ⊢I c1 : ∆1 ∆ ⊢I e : bool, ∆ ⊢I c : ∆′
∆ ⊢I c0 ; c1 : ∆0 [∆1 ] ∆ ⊢I while e do c : ∆′

∆ ⊢I e : bool, ∆ ⊢I c0 : ∆0 , ∆ ⊢I c1 : ∆0
∆ ⊢I if e then c0 else c1 : ∆0

∆ ⊢I d : ∆′ , ∆[∆′ ] ⊢I∪I ′ c : ∆′′


, ∆′ : I ′
′ ′′
∆ ⊢I d; c : ∆ [∆ ]
Tabella 5.3: Semantica statica per i comandi di I MP.

Si noti che abbiamo imposto ai due rami del comando condizionale di


generare lo stesso ambiente di tipi. Se cosı̀ non fosse, non saremmo in
grado di determinare staticamente la correttezza di certi frammenti come

tt or begin if e then var x : int = 4; nil


else var z : bool = tt; nil end(x = 3)

Se la condizione e è falsa l’identificatore x non è definito. Se avessimo


permesso nelle regole la generazione di ∆0 da c0 e ∆1 da c1 mettendo
nella conclusione ∆0 [∆1 ] non avremmo scoperto l’errore staticamente.
Semantica dinamica
Nella valutazione della nuova espressione si esegue inizialmente c e poi,
considerando le modifiche che esso ha apportato alla memoria, si valuta
l’espressione e.

ρ ⊢∆ hc, σi →c hc′ , σ ′ i
ρ ⊢∆ hbegin c result e, σi →e hbegin c′ result e, σ ′ i
88 CAPITOLO 5. COMANDI

ρ ⊢∆ hc, σi →c σ ′
ρ ⊢∆ hbegin c result e, σi →e he, σ ′ i
Inoltre poiché la valutazione delle espressioni può modificare la memo-
ria, dobbiamo rimpiazzare ciascuna transizione ρ ⊢∆ he, σi − →e he′ , σi in
Tab. 3.3 con ρ ⊢∆ he, σi −→e he′ , σ ′ i. 2

Esercizio 5.16. Modificare il linguaggio permettendo solo la dichiarazio-


ne

d ::= var x

Definire quindi un type-checker dinamico.


S OLUZIONE . Supponiamo di aver apportato ad I MP le modifiche fatte
nell’Esercizio 4.2. Definiamo allora gli identificatori liberi e in posizione
di definizione nel seguente modo

F I(var x) = ∅
DI(var x) = {x}

A questo punto si noti che non introducendo i tipi nelle sintassi delle
dichiarazioni, non riusciamo a definire l’ambiente di tipi per gli identi-
ficatori durante l’analisi statica che quindi scompare. Allora dobbiamo
definire l’ambiente ∆ a tempo di esecuzione portandocelo dietro nelle
configurazioni della semantica dinamica che saranno del tipo

→d hd′ , ∆′ , σ ′ i
ρ ⊢I hd, ∆, σi −

Aggiungiamo inoltre un nuovo tipo denotabile che individua le variabili


per le quali non è ancora stato definito il tipo:

DT yp ::= . . . | ⊤

Allora la semantica dinamica della dichiarazione sarà

ρ ⊢I hvar x, ∆, σi →d h[x = l⊥ ], [x = ⊤], σi


5.2. ESERCIZI 89

dove l⊥ indica che la variabile non è stata allocata. Vediamo adesso le


dichiarazioni composte e consideriamo quella sequenziale (quelle privata
e simultanea sono analoghe). Le regole che dobbiamo aggiungere sono

→d hd′0 , ∆′ , σ ′ i
ρ ⊢I hd0 , ∆, σi −
→d hd′0 ; d1, ∆[∆′ ], σ ′ i
ρ ⊢I hd0 ; d1 , ∆, σi −

→d hd′1 , ∆′ , σ ′ i
ρ[ρ0 ] ⊢I∪I0 hd1, ∆, σi −
, ρ0 : I0
→d hρ0 ; d′1 , ∆[∆′ ], σ ′ i
ρ ⊢I hρ0 ; d1 , ∆, σi −

ρ ⊢I hρ0 ; ρ1 , ∆, σi −
→d hρ0 [ρ1 ], ∆, σi
Notiamo che la composizione di ambienti statici avviene solo nelle regole.
Nell’assioma è già stata effettuata induttivamente.
Per quel che riguarda i comandi, la derivazione sarà del tipo

ρ ⊢I hc, ∆, σi →c hc′ , ∆′ , σ ′ i

oppure
ρ ⊢I hc, ∆, σi →c h∆′ , σ ′ i
L’assegnamento rimane pressoché inalterato, però le condizioni di appli-
cabilità si complicano notevolmente

ρ ⊢I he, σi →∗e hk, σi


ρ ⊢I hx := e, ∆, σi →c h∆[x = τ loc], σ[l = k]i

questo se valgono le seguenti condizioni:

∆(e) = τ ∧ (∆(x) = τ loc ∨ ∆(x) = ⊤)

con 
ρ(x) se ρ(x) 6= l⊥ ∧ ρ(x) ∈ Locτ
l=
Newτ (L) se ρ(x) = l⊥ ∧ ∆(x) = ⊤ ∧ σ : L
90 CAPITOLO 5. COMANDI

La condizione ∆(x) = ⊤ serve per assicurare che l’identificatore comun-


que era stato precedentemente dichiarato.
Per concludere, esaminiamo il blocco. Le nuove regole sono

→∗d hρ0 , ∆[∆0 ], σ ′ i


ρ ⊢I hd, ∆, σi −
, ρ0 : ∆0
→c hρ0 ; c, ∆[∆0 ], σ ′ i
ρ ⊢I hd; c, ∆, σi −

→c hc′ , ∆′ , σ ′ i
ρ[ρ0 ] ⊢I∪I0 hc, ∆[∆0 ], σi −
, ρ0 : ∆0
ρ ⊢I hρ0 ; c, ∆[∆0 ], σ ′ i −
→c hρ0 ; c′ , ∆([∆0 ][∆′ ]), σ ′ i

→c h∆′ , σ ′ i
ρ[ρ0 ] ⊢I∪I0 hc, ∆[∆0 ], σi −
, ρ0 : ∆0
→c h∆, σ ′ i
ρ ⊢I hρ0 ; c, ∆[∆0 ], σi −
2

Esercizio 5.17. Definire semantica statica e dinamica della seguente di-


chiarazione

d ::= external x : τ

con semantica intuitiva: se fuori da un blocco un identificatore è definito


external allora in qualsiasi blocco in cui venga ridefinito il valore a cui si
fa riferimento è quello che l’identificatore aveva fuori dal blocco.
S OLUZIONE . La prima considerazione da fare è che per poter inserire que-
sto tipo di modifica bisogna fare riferimento al linguaggio I MP modificato
nell’Esercizio 4.2 che non inizializza gli identificatori quando li dichiara.
Inoltre, come nell’Esercizio 4.4, occorre un nuovo tipo che permetta di
distinguere gli identificatori external dagli altri:

DT yp ::= . . . | τ ext

Gli insiemi DI e F I sono uguali a quelli delle altre dichiarazioni. Vediamo


quindi la semantica statica e dinamica.
5.2. ESERCIZI 91

Semantica statica

∆ ⊢I (external x : τ ) : [x = τ ext]
L’assegnamento non cambia, mentre cambia la semantica statica del bloc-
co.
∆ ⊢I d : ∆0 , ∆′ ⊢I∪I0 c
, ∆0 : I0
∆ ⊢I d; c
dove ∆′ = (∆[∆0 ])[∆|ext ] con I|ext = {x|∆(x) = τ ext} e ∆|ext : I|ext.
Quindi c viene valutato nell’ambiente di tipi modificato dalla dichiarazione
d tranne per quegli identificatori che fuori dal blocco erano stati dichiarati
external per i quali si tiene valida la dichiarazione fatta in precedenza.
Semantica dinamica
Vediamo come viene valutata la nuova dichiarazione:

ρ ⊢∆ hexternal x : τ, σi →d h[x = l], σ[l = ⊥]i, l ∈ Newτ ext (L), σ : L

Il concetto di blocco cambia nello stesso modo in cui è cambiato a livello


statico
ρ ⊢∆ hd, σi →d hd′ , σ ′ i
ρ ⊢∆ hd; c, σi →c hd′ ; c, σ ′i

ρ′ ⊢∆′ hc, σi →c hc′ , σ ′ i


ρ ⊢∆ hρ0 ; c, σi →c hρ0 ; c′ , σ ′ i

ρ′ ⊢∆′ hc, σi →c σ ′
ρ ⊢∆ hρ0 ; c, σi →c σ ′
dove ∆′ è quello definito nella semantica statica e ρ′ = (ρ[ρ0 ])[ρ|ext ] con
ρ|ext definito in modo analogo a ∆|ext .

2
92 CAPITOLO 5. COMANDI

Esercizio 5.18. Dimostrare che vale la seguente implicazione

∆ ⊢I c ∧ ρ ⊢∆ hc, σi →c hc′ , σ ′ i ⇒ ∆ ⊢I c′

Poiché la categoria sintattica dei comandi utilizza quella delle espressioni


e quella delle dichiarazioni, assumiamo validi i risultati degli Esercizi 3.6
e 4.6.
S OLUZIONE . Dimostriamo l’implicazione per induzione sulla struttura
della categoria dei comandi.

BASE
Se c = nil, nessuna transizione della semantica dinamica del tipo richiesto
dal testo è applicabile e dunque l’implicazione è vera.
L’altro caso base è c = x := e. Per ipotesi il comando è ben formato e
quindi sia a e che a x riusciamo ad assegnare un tipo, cioè sono entram-
be ben formate. Infatti se solo una delle due non lo fosse non lo sarebbe
neanche c per la regola

∆ ⊢I e : τ
, ∆(x) = τ loc (5.3)
∆ ⊢I x := e
La regola della semantica dinamica che possiamo applicare è

ρ ⊢∆ he, σi →e he′ , σi
ρ ⊢∆ hx := e, σi →c hx := e′ , σi

Vogliamo quindi dimostrare che c′ = x := e′ è ben formato. Applicando


la regola (5.3) a c′ , deduciamo che questo è ben formato poiché x non è
stata modificata quindi per la regola (5.3) ∆(x) = τ loc, ed e′ : τ per il
risultato dimostrato nell’Esercizio 3.6. Infatti e è ben formata per quanto
detto inizialmente ed e′ è ottenuta da e mediante una applicazione di una
regola della semantica dinamica.

PASSO I NDUTTIVO
Supponiamo che valga l’ipotesi induttiva per i comandi c0 e c1 . Vediamo il
caso in cui c = c0 ; c1 . Per ipotesi tale comando è ben formato, e allora lo
5.2. ESERCIZI 93

sono anche c0 e c1 , perché se anche uno solo dei due non lo fosse, non lo
sarebbe neanche c per la regola

∆ ⊢ I c0 , ∆ ⊢ I c1
(5.4)
∆ ⊢ I c0 ; c1
Per quel che riguarda la semantica dinamica, l’esecuzione di tale comando
consiste nell’esecuzione sequenziale dei suoi sottocomandi. Vediamo il
caso in cui viene eseguito c0 , poi l’altro è analogo.

ρ ⊢∆ hc0 , σi →c hc′0 , σ ′ i
ρ ⊢∆ hc0 ; c1 , σi →c hc′0 ; c1 , σ ′ i

Dunque vogliamo dimostrare che c′ = c′0 ; c1 è ben formato. Applicando la


regola (5.4) a c′ , questo è ben formato se sono ben formati c′0 e c1 . Ma c1
lo è per ipotesi mentre c′0 lo è per ipotesi induttiva.
Esaminiamo ora c = if e then c0 else c1 e supponiamo che sia ben for-
mato, perciò, come nei casi precedenti, lo sono anche e, c0 e c1 , perché se
almeno uno non lo fosse non lo sarebbe neanche c per la regola

∆ ⊢I e : bool, ∆ ⊢I c0 , ∆ ⊢I c1
∆ ⊢I if e then c0 else c1
Per quel che riguarda la semantica dinamica, ci sono due possibilità a
seconda del valore associato ad e
ρ ⊢∆ he, σi →∗e htt, σi
ρ ⊢∆ hif e then c0 else c1 , σi →c hc0 , σi

ρ ⊢∆ he, σi →∗e hff, σi


ρ ⊢∆ hif e then c0 else c1 , σi →c hc1 , σi
In entrambi i casi il comando transisce in comandi che, per ipotesi, sono
ben formati.
Consideriamo c = while e do c0 . Supponiamo che sia ben formato allora
94 CAPITOLO 5. COMANDI

per le solite considerazioni lo sono anche e e c0 . Se il valore di e è falso,


la configurazione in cui il comando transisce è terminale, dunque non è
una transizione del tipo richiesto delle ipotesi dell’implicazione, per questo
l’implicazione è vera. Vediamo ora la regola nel caso in cui e valga vero

ρ ⊢∆ he, σi →∗e htt, σi


ρ ⊢∆ hwhile e do c0 , σi →c hc0 ; while e do c0 , σi

per vedere che adesso c′ = c0 ; while e do c0 è ben formato basta dimo-


strare che lo sono i comandi che lo compongono. Notiamo che sia c0 che
il while lo sono per ipotesi.
Vediamo infine il caso del blocco, c = d; c0 . Per ipotesi il blocco è ben
formato e per le solite considerazioni lo sono anche d e c0 . Per quel
che riguarda la semantica dinamica l’esecuzione del comando consiste
nell’elaborazione di d e poi nell’esecuzione di c0 . Vediamo i due casi
distinti

ρ ⊢∆ hd, σi →d hd′ , σi
ρ ⊢∆ hd; c0, σi →c hd′ ; c0 , σi

In questo caso il comando c′ = d′ ; c0 è ben formato. Infatti d′ lo è per il


risultato ottenuto nell’Esercizio 4.6, in quanto d è ben formata per ipotesi
e d′ è ottenuta da d mediante l’applicazione di una regola della semantica
dinamica; c0 lo è per ipotesi. Vediamo l’altro caso

ρ ⊢∆ hc0 , σi →c hc′0 , σ ′ i
ρ ⊢∆ hρ0 ; c0 , σi →c hρ0 ; c′0 , σ ′ i

In tal caso, perché il nuovo comando sia ben formato, è sufficiente che
lo sia c′0 in quanto l’altra componente è un ambiente al quale possiamo
sempre associare l’ambiente statico compatibile. Ma c′0 è ben formato per
ipotesi induttiva essendo stato ottenuto da c0 mediante una applicazione di
una regola della semantica dinamica ed essendo c0 ben formato per ipotesi
induttiva. 2
5.2. ESERCIZI 95

Esercizio 5.19. Dato un comado c e un ambiente statico ∆ dimostrare che


vale la seguente implicazione

x0 ∈ F I(c) \ Dom(∆) ⇒ ∆ 6⊢I c

assumendo i risultati dimostrati negli esercizi 3.7 e 4.7.


S OLUZIONE . Dimostriamo l’implicazione per induzione sulla struttura dei
comandi.

BASE
Consideriamo c = nil. In tal caso x0 6∈ F I(nil) dunque l’implicazione è
sempre vera.
L’altro caso base consiste in c = x := e. Per ipotesi x0 ∈ F I(x := e) =
{x} ∪ F I(e). In questa situazione il comando c non è ben formato poiché
l’unica regola che potremmo applicare

∆ ⊢I e : τ
, ∆(x) = τ loc
∆ ⊢I x := e

ha la condizione a margine non verificata se x = x0 , poiché x0 6∈ Dom(∆),


oppure ha la premessa non verificata se x0 ∈ F I(e) per il risultato dimo-
strato nell’Esercizio 3.7.

PASSO I NDUTTIVO
Consideriamo c = c0 ; c1 . Per ipotesi x0 ∈ F I(c0; c1 ) = F I(c0 ) ∪ F I(c1 ).
Dunque per almeno uno dei due comandi vale l’ipotesi induttiva e dun-
que almeno uno dei comandi non è ben formato implicando che anche c
non è ben formato in base all’unica regola applicabile per la composizione
sequenziale
∆ ⊢ I c0 , ∆ ⊢ I c1
∆ ⊢ I c0 ; c1
dove almeno una delle due premesse è falsa.
Consideriamo ora c = if e then c0 else c1 e supponiamo che x0 ∈
F I(c) = F I(e) ∪ F I(c0 ) ∪ F I(c1). In tal caso x0 appartiene ad almeno
uno dei tre insiemi e quindi l’ipotesi induttiva vale per almeno uno dei tre
96 CAPITOLO 5. COMANDI

sottotermini. Per questo almeno una delle premesse della seguente regola
è falsa
∆ ⊢I e : bool, ∆ ⊢I c0 , ∆ ⊢I c1
∆ ⊢I if e then c0 else c1
quindi il comando c non è ben formato.
Analoga è la dimostrazione per c = while e do c0 .

Consideriamo infine il caso in cui c = d; c0 e supponiamo che x0 ∈


F I(d; c0 ) = F I(d) ∪ (F I(c0 ) \ DI(d)). In questo contesto x0 appartiene
ad almeno uno dei due insiemi dell’unione. Se x0 ∈ F I(d), per il risultato
dimostrato nell’Esercizio 4.7 non riusciamo ad associare un ambiente sta-
tico al blocco. Invece se x0 ∈ F I(c0)\DI(d) si ha che x0 6∈ Dom(∆[∆′ ]),
dove ∆′ è l’ambiente associato a d, e dunque per ipotesi induttiva c0 non
è ben formato nell’ambiente ∆[∆′ ]. Dunque l’unica regola che possia-
mo applicare, anche in questo caso, ha una premessa non verificata ben
formato
∆ ⊢I d : ∆′ , ∆[∆′ ] ⊢I∪I ′ c0
, ∆′ : I ′
∆ ⊢I d; c0
e possiamo concludere che il blocco non è ben formato. 2
Capitolo 6

Procedure

L’idea che sta alla base delle procedure è quella di abbreviare la scrittura
di programmi che contengono sequenze ripetute di comandi uguali e che
differiscono al più per i dati su cui operano. L’astrazione sui dati viene
implementata mediante il meccanismo di passaggio dei parametri. Essen-
zialmente, leghiamo negli ambienti una sequenza di comandi (il corpo del-
la procedura) ad un identificatore (il nome della procedura). Le procedure
implementano il principio di astrazione sui comandi. Trattiamo in questo
capitolo le procedure cosı̀ come definite in I MP fornendo la loro semanti-
ca statica e dinamica. Proseguiamo quindi fornendo la soluzione di alcuni
esercizi proposti.

6.1 Semantica delle procedure

Riportiamo prima le definizioni di F I e DI.

Definizione 6.1 (identificatori liberi). La funzione che ad ogni dichiara-


zione e comando riguardante le procedure associa l’insieme degli identifi-

97
98 CAPITOLO 6. PROCEDURE

catori liberi in essi contenuti è definita per induzione da

F I(procedure p(f orm)c) = F I(c) \ DI(f orm)


F I(f orm = ae) = F I(ae)
F I(p(ae)) = F I(ae) ∪ {p}
F I(•) = ∅
F I(f orm) = ∅
F I(e, ae) = F I(e) ∪ F I(ae)

Definizione 6.2 (identificatori in posizione di definizione). La funzione che


ad ogni dichiarazione e comando riguardante le procedure associa l’insie-
me degli identificatori in posizione di definizione in essi contenuti è definita
per induzione da

DI(procedure p(f orm)c) = {p} ∪ DI(f orm) ∪ DI(c)


DI(f orm = ae) = DI(f orm)
DI(p(ae)) = ∅
DI(•) = ∅
DI(ae) = ∅
DI(const x : τ ) = DI(var x : τ ) = {x} ∪ DI(f orm)

Definiamo quindi la semantica statica il cui scopo è quello di associa-


re un ambiente di tipi alla lista dei parametri formali, una lista di tipi ai
parametri attuali, di verificare che i tipi dei formali e degli attuali siano
coincidenti nella chiamata di procedura e, infine, di controllare se il corpo
della procedura è ben formato. Le regole sono riportate in Tab. 6.1.
Per discutere la semantica statica delle procedure partiamo dalla di-
chiarazione di procedura. La prima regola associa all’identificatore di pro-
cedura p il tipo T (f orm)proc nel caso in cui la lista di parametri for-
mali, cosı̀ come il corpo della procedura siano ben formati. La funzione
T : F orm → AT yp restituisce la lista dei tipi dei parametri formali. Il
tipo di p è quindi aetproc (si noti che il tipo degli attuali e dei formali è
lo stesso). La funzione T è definita per induzione strutturale sulla sintassi
di Form considerando come caso base la lista col solo elemento • che è
definito di tipo •. La seconda regola in Tab. 6.1 riguarda la chiamata di
6.1. SEMANTICA DELLE PROCEDURE 99

f orm : ∆0 , ∆[∆0 ] ⊢I∪I0 c


∆ ⊢I procedure p(f orm)c : [p = T (f orm)proc]

∆ ⊢I ae : aet
, ∆(p) = aetproc
∆ ⊢I p(ae)

f orm : ∆0 , ∆[∆0 ] ⊢I∪I0 c


, ∆0 : I0
∆ ⊢I c

f orm : ∆0 , ∆ ⊢I ae : T (f orm)
∆ ⊢I f orm = ae : ∆0


 T (•) = •



T (const x : τ, f orm) = τ, T (f orm)




T (var x : τ, f orm) = τ loc, T (f orm)



 ∆ ⊢I • : •


 ∆ ⊢I e : τ, ∆ ⊢I ae : aet


∆ ⊢I e, ae : τ, aet

•:∅






f orm : ∆0



, ∆0 : I0 , x 6∈ I0


const x : τ, f orm : ∆0 [x = τ ]






 f orm : ∆0


 ∆0 : I0 , x 6∈ I0
var x : τ, f orm : ∆0 [x = τ loc]

Tabella 6.1: Semantica statica per le procedure di I MP.


100 CAPITOLO 6. PROCEDURE

procedura e determina la sua ben formatezza. Essa esegue un controllo


implicito della corrispondenza dei tipi tra parametri attuali e formali chie-
dendo che ∆(p) = aetproc dove la lista di tipi aet è quella associata ai
parametri attuali nella premessa, mentre la lista dei tipi associata a p da
∆ è determinata al momento della dichiarazione della procedura dalla li-
sta dei parametri formali. Quindi la condizione imposta per l’applicazione
della regola garantisce che queste due liste di tipi siano uguali, cioè che
aet = T (f orm). Per poter applicare la regola della chiamata dobbiamo
deteminare la lista aet dei tipi dei parametri attuali. Tale lista è costruita
induttivamente partendo dal suo terminatore • e l’assioma ∆ ⊢I • : • me-
diante il quinto gruppo di regole. La terza regola controlla che il corpo c
della procedura sia ben formato nell’ambiente corrente ∆. Per far questo
tiene conto dell’ambiente statico ∆0 generato dalla lista f orm dei parame-
tri formali. La quarta regola, infine, associa alla dichiarazione f orm = ae
l’ambiente statico generato da f orm nel caso in cui le liste f orm ed ae
siano ben formate.
Ci rimane da descrivere come f orm genera l’ambiente ∆0 . Questo av-
viene per induzione su f orm partendo dal suo terminatore • mediante il
sesto gruppo di regole. Si noti che non abbiamo prefissato ∆ ⊢I alla pre-
messa ed alla conclusione delle regole per evidenziare che F V (f orm) = ∅
e quindi non dobbiamo mai ricorrere a ∆ nella sua elaborazione. L’assioma
associa l’ambiente vuoto al terminatore, mentre le altre due regole associa-
no x a τ o τ loc a seconda che si abbia una dichiarazione di tipo const o
di tipo var.
In Tab. 6.2 sono riportate le regole per la semantica dinamica delle
procedure. Consideriamo prima l’unica regola che riguarda la dichiara-
zione della procedura che genera l’ambiente dinamico [p = λf orm.c′ ]
senza modificare la memoria. Il valore denotabile che associamo a p è
chiamato chiusura o astrazione. Notiamo che, a seconda del tipo di sco-
ping con cui si lavora, c′ è definito in modo diverso. In caso di scoping
statico il corpo c′ dell’astrazione è ρ′ ; c dove c è il corpo della procedura
e ρ′ = ρ|F I(c)\DI(f orm) . L’ambiente ρ′ consente di risolvere i riferimenti
agli identificatori liberi in c nella chiamata, utilizzando i legami presen-
ti nell’ambiente dinamico quando la procedura era stata dichiarata. Nel
caso dello scoping dinamico invece c′ = c con c corpo della procedura;
6.1. SEMANTICA DELLE PROCEDURE 101

ρ ⊢∆ hprocedure p(f orm)c, σi − →d h[p = λf orm.c′], σi,


 ′
c = ρ|F I(c)\DI(f orm) ; c scoping statico
c′ = c scoping dinamico

→c hf orm = ae; c′ , σi, ρ(p) = λf orm.c′


ρ ⊢∆ hp(ae), σi −

→e he′ , σi
ρ ⊢∆ he, σi −
→ae h(e′ , ae), σi
ρ ⊢∆ h(e, ae), σi −

→ae hae′ , σi
ρ ⊢∆ hae, σi −
→ae h(k, ae′ ), σi
ρ ⊢∆ h(k, ae), σi −

→ae hae′ , σi
ρ ⊢∆ hae, σi −
→d hf orm = ae′ , σi
ρ ⊢∆ hf orm = ae, σi −

ak, L ⊢ f orm : ρ0 , σ0
ρ ⊢∆ hf orm = ak, σi −
→d hρ0 , σ[σ0 ]i

•, L ⊢ • : ∅, ∅






ak, L ⊢ f orm : ρ0 , σ0





(k, ak), L ⊢ const x : τ, f orm : ρ0 [x = k], σ0






 ak, L ∪ {l} ⊢ f orm : ρ0 , σ0


 , l ∈ Newτ (L0 ), σ0 : L0
(k, ak), L ⊢ var x : τ, f orm : ρ0 [x = l], σ0 [l = k]

Tabella 6.2: Semantica dinamica per le procedure di I MP.


102 CAPITOLO 6. PROCEDURE

in tal modo al momento dell’esecuzione di c si utilizzeranno solo i valori


associati agli identificatori al momento della chiamata della procedura.

La regola successiva definisce la chiamata permettendo di effettuare


sia l’associazione tra i parametri formali ed attuali sia di eseguire il corpo
dell’astrazione associata alla procedura.

Le due regole successive sono quelle che eseguono la valutazione delle


espressioni contenute nella lista dei parametri attuali ottenendo la lista ak,
lista dei valori corrispondenti alle espressioni in ae.

Le ulteriori due regole riguardano l’elaborazione di f orm = ae in-


trodotta dalla regola per la chiamata di procedura. La prima di queste
permette di effettuare la valutazione di ae all’interno della dichiarazione
f orm = ae. L’altra regola determina l’ambiente ρ0 e la nuova memo-
ria σ[σ0 ] ottenuti dall’elaborazione di f orm = ae. Per poter applicare
queste regole abbiamo bisogno di un predicato ak, L ⊢ f orm : ρ, σ, con
L ⊆ Loc, che determina l’ambiente ρ e la memoria σ ottenuti elaborando
f orm = ae isolatamente. Intuitivamente, L è l’insieme di locazioni dispo-
nibili per implementare il passaggio dei parametri. L’assioma per il nuovo
predicato associa l’ambiente ∅ e la memoria ∅ con il terminatore della li-
sta dei formali • indipendentemente dall’insieme L di locazioni. Nel caso
di un formale di tipo const estendiamo l’ambiente della premessa con
il nuovo legame x = k e lasciamo invariata la memoria σ0 . Infine se il
parametro è di tipo var estendiamo l’ambiente nella premessa con il le-
game x = l, dove l è una nuova locazione di tipo τ contenuta in L che
andiamo ad eliminare da L nella conclusione perché non venga più consi-
derata disponobile. Inoltre modifichiamo la memoria inizializzando a k la
nuova locazione. Si noti che in entrambi i casi (const e var) facciamo
una copia del parametro attuale nel nuovo ambiente e nella nuova memo-
ria. Quindi ogni modifica eseguita sui parametri all’interno del corpo della
procedura non sarà visibile all’esterno. Questo meccanismo di passaggio
dei parametri è noto come passaggio per valore. La sola differenza tra i
parametri const ed i parametri var è che i primi non possono nemmeno
essere modificati nel corpo della procedura.
6.2. ESERCIZI 103

6.2 Esercizi

Esercizio 6.1. Dato il programma

const x : int = 4;
procedure p(var y : int) y := y ∗ x; print(y);
procedure q(•) const x : int = 2; p(2);
begin
q(•); p(2);
end

dove le parole chiave begin e end servono solo da parentesi sintattiche e


la sintassi del comando print, assunta come data, è corrispondente all’in-
tuizione. Dire qual è il suo output in caso di scoping statico e in caso di
scoping dinamico e dimostrare la risposta.

S OLUZIONE . Innanzitutto ricordiamo la differenza tra scoping statico e


dinamico; nel primo si risolvono i legami a tempo di compilazione, quindi
per le procedure questo significa che esse vengono valutate nell’ambiente
della chiamata esteso con l’ambiente presente al momento della loro di-
chiarazione per risolvere i riferimenti alle variabili libere del corpo. Nello
scoping dinamico, invece, i legami vengono risolti a tempo di esecuzione,
quindi, per ciò che riguarda le procedure, l’ambiente che si considera è
quello presente al momento della loro chiamata.

Scoping statico
Vediamo le derivazioni che vengono eseguite nel caso dello scoping sta-
tico, considerando un ambiente dinamico iniziale ρ compatibile con un
ambiente di tipi ∆. Per rendere più compatta la scrittura diamo dei nomi
104 CAPITOLO 6. PROCEDURE

alle dichiarazioni e ai comandi del frammento considerato

d1 = const x : int = 4;
d2 = procedure p(var y : int) y := y ∗ x; print(y);
c2 = y := y ∗ x; print(y);
d3 = procedure q(•) const x : int = 2; p(2);
c3 = const x : int = 2; p(2);
d = d2 ; d3
c1 = q(•); p(2);

La prima cosa da fare è valutare la dichiarazione d1

ρ ⊢∆ hconst x : int = 4, σi →d h[x = 4], σi


ρ ⊢∆ hconst x : int = 4; d, σi →d h[x = 4]; d, σi
ρ ⊢∆ hconst x : int = 4; d; c1, σi →c h[x = 4]; d; c1, σi

A questo punto ρ0 = ρ[x = 4] è l’ambiente in cui verrà elaborata la


dichiarazione della procedura p (compatibile con ∆0 = ∆[x = int])

ρ0 ⊢∆0 hprocedure p(var y : int) c2 , σi →d


h[p = λ var y : int.[x = 4]; c2 ], σi
ρ ⊢∆ hρ0 ; procedure p(var y : int) c2 , σi →d
hρ0 ; [p = λ var y : int.[x = 4]; c2 ], σi
ρ ⊢∆ hρ0 ; procedure p(var y : int) c2 ; d3 , σi →d
hρ0 ; [p = λ var y : int.[x = 4]; c2 ]; d3 , σi
ρ ⊢∆ hρ0 ; procedure p(var y : int) c2 ; d3; c1 , σi →c
hρ0 ; [p = λ var y : int.[x = 4]; c2 ]; d3 ; c1 , σi

Nella astrazione che asociamo a p nell’ambiente dinamico abbiamo inse-


rito il legame [x = 4] perché x è un identificatore libero del corpo c2 della
procedura. Chiamiamo ρ′ = [p = λ var y : int.[x = 4]; c2 ], ed essendo
le dichiarazioni sequenziali l’ambiente in cui devo valutare la successiva
dichiarazione della procedura è ρ1 = ρ[ρ0 [ρ′ ]] il cui ambiente di tipi è
6.2. ESERCIZI 105

∆1 = ∆0 [p = intproc].

ρ1 ⊢∆1 hprocedure q(•) c3 , σi →d h[q = λ • .ρ′ ; c3 ], σi


ρ ⊢∆ hρ0 [ρ′ ]; procedure q(•) c3 , σi →d hρ0 [ρ′ ]; ρ′′ , σi
ρ ⊢∆ hρ0 [ρ′ ]; procedure q(•) c3 ; c1 , σi →c hρ0 [ρ′ ]; ρ′′ ; c1 , σi

dove ρ′′ = [q = λ • .ρ′ ; c3 ]. Qui abbiamo incluso ρ′ nell’astrazione asso-


ciata a q poiché p = Dom(ρ) è un identificatore libero in c3 . Adesso devo
eseguire c1 nell’ambiente ρ[ρ0 [ρ′ [ρ′′ ]]] = ρ2 . In questo ambiente viene
chiamata la procedura q

ρ2 ⊢∆2 hq(•), σi →c h• = •; ρ′′ ; c3 , σi

dove ∆2 = ∆1 [q = •proc] è l’ambiente di tipi compatibile con ρ2 , men-


tre ρ′′ è l’ambiente associato a q dalla sua dichiarazione. Adesso quin-
di in ρ3 = ρ2 [ρ′′ ] (la dichiarazione • = • non ha alcuna influenza né
sull’ambiente né sulla memoria) eseguiamo c3

ρ3 ⊢∆3 hconst x : int = 2, σi →d h[x = 2], σi


ρ3 ⊢∆3 hconst x : int = 2; p(2), σi →c h[x = 2]; p(2), σi

dove ∆3 è l’ambiente di tipi corrispondente a ρ3 , ed è uguale a ∆2 avendo


definito un identificatore già esistente. Notiamo che la memoria σ da cui
eseguiamo c3 è ancora quella iniziale poiché né le dichiarazioni di iden-
tificatori costanti né quelle di procedura la influenzano. A questo punto,
anche se la chiamata p(2) viene eseguita nell’ambiente ρ3 [x = 2], l’ese-
cuzione della chiamata effettua l’associazione tra formali ed attuali e poi
esegue il corpo del comando risolvendo gli identificatori liberi nel bloc-
co individuato dall’ambiente associato alla procedura nel momento della
dichiarazione, [x = 4].

ρ3 [x = 2] ⊢∆3 hp(2), σi →c hvar y : int = 2; [x = 4]; c2 , σi

dove [x = 4] è l’ambiente registrato nell’astrazione associata a p al mo-


mento della sua dichiarazione. Perciò dal corpo di q possiamo notare che
al momento della chiamata si ha y = 2 ma x = 4 e, allora, print(y) stampa
106 CAPITOLO 6. PROCEDURE

in output il valore 8. Tornando al corpo del programma si ha la chiama-


ta p(2) nell’ambiente ρ3 che assegna il valore 4 a x e quindi il comando
print(y) restituisce il valore 8.
Scoping dinamico
Riportiamo esplicitamente solo le derivazioni che cambiano in modo rile-
vante sottolineando la differenza con lo scoping statico.

ρ0 ⊢∆0 hprocedure p(var y : int) c2 , σi →d


h[p = λ var y : int.c2 ], σi
ρ ⊢∆ hρ0 ; procedure p(var y : int) c2 , σi →d
hρ0 ; [p = λ var y : int.c2 ], σi
ρ ⊢∆ hρ0 ; procedure p(var y : int) c2 ; d3 , σi →d
hρ0 ; [p = λ var y : int.c2 ]; d3 , σi
ρ ⊢∆ hρ0 ; procedure p(var y : int) c2 ; d3; c1 , σi →c
hρ0 ; [p = λ var y : int.c2 ]; d3 ; c1 , σi

Notiamo che la dichiarazione della procedura non registra nell’astrazio-


ne legata al nome della procedura nessun ambiente e dunque, al momen-
to della chiamata, i suoi identificatori liberi verranno risolti nell’ambiente
corrente. Vediamo l’elaborazione della dichiarazione di q, sempre nel-
l’ambiente modificato in modo opportuno dalle dichiarazioni precedenti,
cioè in ρ1 = ρ[ρ0 [ρ′ ]] dove ρ0 è quello definito in precedenza e adesso
ρ′ = [p = λ var y : int.c2 ]

ρ1 ⊢∆1 hprocedure q(•) c3 , σi →d h[q = λ • .c3 ], σi


ρ ⊢∆ hρ0 [ρ′ ]; procedure q(•) c3 , σi →d hρ0 [ρ′ ]; ρ′′ , σi
ρ ⊢∆ hρ0 [ρ′ ]; procedure q(•) c3 ; c1 , σi →c hρ0 [ρ′ ]; ρ′′ ; c1 , σi

dove ρ′′ = [q = λ • .c3 ]. In questo caso le procedure non si portano dietro


ambienti, dunque quando, dentro q, chiamiamo p si ha x = 2 e perciò
l’output è y = 4. Invece quando p viene chiamata dal corpo del programma
abbiamo x = 4 e quindi l’output è y = 8. Vediamo in particolare le regole
delle chiamate di q e p

ρ2 ⊢∆2 hq(•), σi →c h• = •; c3 , σi
6.2. ESERCIZI 107

ρ3 [x = 2] ⊢∆3 hp(2), σi →c hvar y : int = 2; c2 , σi


dove ρ2 = ρ[ρ0 [ρ′ [ρ′′ ]]] e ρ3 = ρ2 [ρ′′ ]. 2

Esercizio 6.2. Si consideri il seguente programma

var x : bool = tt;


const y : bool = tt;
procedure p(•) x := z or y;
const y : bool = ff;
p(•)

Dire quale errore determina la semantica statica fornendo le derivazioni.


Ignorando l’errore rilevato staticamente, sotto quali condizioni l’esecuzio-
ne del programma non produce un errore a tempo di esecuzione?
S OLUZIONE . Per maggiore chiarezza, assegnamo dei nomi ad ogni dichia-
razione:

d1 = var x : bool = tt;


d2 = const y : bool = tt;
d3 = procedure p(•) x := z or y;
d4 = const y : bool = ff;
d = d1 ; d2 ; d3 ; d4

Dobbiamo eseguire un’analisi statica di d; p(•), che rappresenta il pro-


gramma fornito dal testo. Consideriamo un ambiente di tipi iniziale ∅ a
partire dal quale applichiamo le regole della semantica statica.
∅ ⊢∅ d1 : [x = boolloc], [x = boolloc] ⊢{x} d2 : [y = bool]
∅ ⊢∅ (d1 ; d2 ) : [x = boolloc][y = bool]

∅ ⊢∅ (d1 ; d2) : [x = boolloc][y = bool],


[x = boolloc][y = bool] ⊢{x,y} d3 : [p = •proc]
∅ ⊢∅ (d1 ; d2 ; d3 ) : [x = boolloc][y = bool][p = •proc]
108 CAPITOLO 6. PROCEDURE

Chiamiamo ∆ = [x = boolloc, y = bool, p = •proc] = [x = boolloc][y =


bool][p = •proc] e I = {x, y, p}; in questo ambiente di tipi dobbiamo
esaminare staticamente il corpo della procedura p, che chiamiamo c. La
regola di inferenza da utilizzare è

• : ∅, ∆ ⊢I c
, ∆(p) = •proc
∆ ⊢I c
Però non riusciamo ad assegnare un tipo a z in quanto non viene definita
nel frammento fornito. Dunque il comando c non è ben formato poiché
non è ben formato l’assegnamento in esso contenuto. A questo punto del-
l’analisi statica viene generato un errore.
Vediamo ora come può essere evitato l’errore a livello dinamico. L’errore è
generato quando si tenta di valutare la variabile z inserita in una operazione
di or. Sappiamo però che, in tale operazione, se uno dei due sottotermini
che viene valutato è vero, allora il risultato restituito è tt, indipendentemen-
te dal valore dell’altro sottotermine che, quindi, può non essere valutato.
Perciò per evitare l’errore è necessario sostituire le regole per la valutazio-
ne di or del nostro linguaggio che impongono un ordine di valutazione da
destra a sinistra con le seguenti che impongono l’ordine inverso

ρ ⊢∆ he1 , σi →e he′1 , σi
ρ ⊢∆ he0 or e1 , σi →e he0 or e′1 , σi

ρ ⊢∆ he1 , σi →e htt, σi
ρ ⊢∆ he0 or e1 , σi →e htt, σi

ρ ⊢∆ he1 , σi →e hff, σi
ρ ⊢∆ he0 or e1 , σi →e he0 , σi
Le nuove regole non sono sufficienti ad evitare l’errore. Infatti se l’ese-
cuzione della procedura risolve i riferimenti alle variabili libere a partire
dall’ambiente dinamico definito nel momento della sua dichiarazione (sco-
ping statico) il valore di y è tt e dunque la variabile z non viene valutata
6.2. ESERCIZI 109

evitando la generazione dell’errore. Se, invece, la procedura viene esegui-


ta con scoping dinamico risulta che y vale ff e dunque, quando si valuta
l’or si cerca di valutare la variabile z generando un errore. Quindi si rende
necessario nella nostra situazione utilizzare lo scoping statico. 2

Esercizio 6.3. Modificare I MP in modo da poter passare le procedure come


parametri di altre procedure. Definire, poi, semantica statica e dinamica
delle modifiche apportate.
S OLUZIONE . Per aggiungere questa nuova possibilità dobbiamo modifica-
re la definizione dei parametri formali ed attuali in modo che ammettano
anche procedure. Per quel che riguarda i parametri formali, dobbiamo ag-
giungere un costrutto che corrisponda alla dichiarazione di procedura; que-
st’operazione implementa il Principio di Corrispondenza in base al quale
un linguaggio, per essere il più chiaro possibile deve avere una corrispon-
denza tra i meccanismi di passaggio dei parametri e i meccanismi di dichia-
razione degli identificatori. Quindi aggiungiamo alla sintassi la seguente
produzione

f orm ::= . . . |proc p : aetproc, f orm

A questo punto, per mantenere la corrispondenza tra parametri formali e


attuali, dobbiamo fare un’operazione analoga nella definizione dei parame-
tri attuali valutati. Il problema che si presenta è quello di trovare un modo
adeguato per rappresentare una procedura che si trova tra i parametri di
un’altra procedura; per fare questo usiamo le astrazioni, introdotte proprio
come “valore” di una procedura

ak ::= . . . |λ f orm.c, ak

Avendo aggiunto un valore agli attuali dobbiamo modificare anche la defi-


nizione dei tipi degli attuali aet

aet ::= . . . | aetproc, aet

Non dobbiamo invece modificare la definizione degli attuali ae poiché pas-


siamo come parametro procedurale solo l’identificatore di procedura che
110 CAPITOLO 6. PROCEDURE

poi verrà valutato ad una astrazione. Infine dobbiamo modificare la fun-


zione T che determina la lista di tipi di f orm aggiungendo alla definizione
per induzione strutturale in Tab. 6.1 il caso
T (proc p : aetproc, f orm) = aetproc, T (f orm)
Fatte tutte le modifiche sintattiche al linguaggio che permettono di passare
le procedure come parametri di altre procedure, definiamo, per i nuovi co-
strutti, gli identificatori liberi, quelli in posizione di definizione e le nuove
regole di inferenza della semantica statica e dinamica.
F I(proc p : aetproc, f orm) = ∅
DI(proc p : aetproc, f orm) = {p} ∪ DI(f orm)
Semantica statica
Prima di tutto dobbiamo assegnare un ambiente di tipi alle dichiarazioni
contenute nei parametri formali e quindi dobbiamo dire come viene modi-
ficato l’ambiente da un parametro formale che rappresenta una procedura
f orm : ∆0
, ∆0 : I0 , p 6∈ I0
(proc p : aetproc, f orm) : ∆0 [p = aetproc]
La condizione p 6∈ I0 assicura che l’identificatore p non compare tra i
formali già esaminati.
Semantica dinamica
Vediamo, ora, come viene modificato il predicato sui parametri attuali che
definisce l’ambiente dinamico e la memoria associata alla lista degli attuali.
La regola che dobbiamo aggiungere consente di estendere l’ambiente nella
premessa con il legame tra un identificatore di procedura ed una astrazione
lasciando inalterata la memoria.
ak, L ⊢ f orm : ρ0 , σ0
(λ f orm.c, ak), L ⊢ (proc p : aetproc, f orm) : ρ0 [p = λ f orm.c], σ0
Infine tra le regole di valutazione dei parametri attuali dobbiamo aggiun-
gere quella che valuta i parametri procedurali
ρ ⊢∆ h(x, ae), σi →ae h(λ f orm.c, ae), σi, ρ(x) = λf orm.c
6.2. ESERCIZI 111

e quella che consente di proseguire la valutazione dopo aver trovato un


parametro procedurale
ρ ⊢∆ hae, σi →ae hae′ , σ ′ i
ρ ⊢∆ h(λ f orm.c, ae), σi →ae h(λ f orm.c, ae′ ), σ ′ i
2

Esercizio 6.4. Modificare I MP per consentire il passaggio dei parametri


per riferimento; definire la semantica statica e dinamica dei costrutti che
vengono aggiunti.
S OLUZIONE . Ricordiamo che il passaggio per riferimento consiste nel
passare come parametri attuali delle locazioni e quindi le modifiche fatte
nel corpo della procedura sui parametri attuali saranno visibili anche all’e-
sterno al ritorno dalla chiamata.
Dobbiamo prima modificare la definizione dei parametri formali e di quelli
attuali. Ai parametri formali dobbiamo aggiungere una forma che ci per-
metta di identificare i parametri passati per riferimento; in quelli attuali
specifichiamo il fatto che nella lista dei parametri possiamo anche trovare
una locazione
f orm ::= . . . |ref x : τ, f orm
ae ::= . . . |l, ae
Definiamo, quindi, il tipo della lista degli attuali
aet ::= . . . |τ loc, aet
A questo punto dobbiamo vedere quali sono gli identificatori liberi e quali
sono quelli in posizione di definizione nel nuovo costrutto
F I(ref x : τ, f orm) = ∅
DI(ref x : τ, f orm) = {x} ∪ DI(f orm)
Modifichiamo, infine, la funzione di assegnamento di tipo ai parametri
formali
T (ref x : τ, f orm) = τ loc, T (f orm)
112 CAPITOLO 6. PROCEDURE

Possiamo, quindi, definire la semantica dei nuovi costrutti.


Semantica statica
Vediamo cosa succede per i formali. Dobbiamo aggiungere una regola
alla definizione del predicato che associa un ambiente statico a f orm per
gestire i parametri per riferimento.

f orm : ∆0
, ∆0 : I0 ; x 6∈ I0
(ref x : τ, f orm) : ∆0 [x = τ loc]

Vediamo ora le regole da aggiungere per gli attuali

∆ ⊢I ae : aet
, l ∈ Locτ
∆ ⊢I l, ae : τ loc, aet

Semantica dinamica
La semantica dinamica è un pò più complessa da definire in quanto, per
determinare le configurazioni, abbiamo bisogno di capire in quale modo un
parametro attuale deve essere utilizzato. Per questo introduciamo l’insieme
sintattico M ODI con metevariabile µ definito come

µ ::= •|val, µ|ref, µ

dove val indica il passaggio dei parametri per valore utilizzato in I MP


e ref il nuovo passaggio per riferimento. Definiamo, ora la funzione
M : aet → M ODI come segue

M(•) = •
M(τ, aet) = val, M(aet)
M(τ loc, aet) = ref, M(aet)

la quale ci permette di associare ad una lista di tipi la lista dei modi in cui
i corrispondenti parametri devono essere passati. Definiamo le configura-
zioni per i parametri attuali

Γ∆,µ = {hae, σi|∃aet.∆ ⊢I ae : aet} ∪ {hak, σi}, M(aet) = µ


6.2. ESERCIZI 113

Definiamo, quindi, la semantica dinamica portandoci dietro, nelle regole,


il modo µ nel caso in cui si stia valutando una lista di parametri attuali
ρ ⊢∆,µ hae, σi →ae hae′ , σ ′ i
Adesso, se il passaggio è per valore le regole sono analoghe a quelle che
abbiamo nel nostro linguaggio
ρ ⊢∆ he, σi →e he′ , σ ′ i
ρ ⊢∆,(val,µ) h(e, ae), σi →ae h(e′ , ae), σ ′ i

ρ ⊢∆ hae, σi →ae hae′ , σ ′ i


ρ ⊢∆,(val,µ) h(k, ae), σi →ae h(k, ae′ ), σ ′ i
Se invece il passaggio è per riferimento quando incontriamo un identifica-
tore x dobbiamo assegnargli come valore una locazione
ρ ⊢∆,(ref,µ) h(x, ae), σi →ae h(l, ae), σ ′ i, l = ρ(x)
ρ ⊢∆ hae, σi →ae hae′ , σ ′ i
ρ ⊢∆,(ref,µ) h(l, ae), σi →ae h(l, ae′ ), σ ′ i
A questo punto possiamo definire le regole che associano parametri formali
e attuali. La prima consente di valutare gli attuali garantendo, attraverso i
modi, le corrispondenze dei loro tipi con quelli dei formali.
ρ ⊢∆,µ hae, σi →e hae′ , σ ′ i
, µ = M(T (f orm))
ρ ⊢∆ hf orm = ae, σi →d hf orm = ae′ , σ ′ i
La regola che consente invece di associare un ambiente alla dichiarazione
f orm = ae non cambia rispetto a quella fornita per I MP. Infine vediamo
quale ambiente viene associato al nuovo tipo di parametro formale
ak, L ⊢ f orm : ρ0 , σ0
(l, ak), L ⊢ ref x : τ, f orm : ρ0 [x = l], σ0
2
114 CAPITOLO 6. PROCEDURE

Esercizio 6.5. Dimostrare che l’implicazione dell’Esercizio 4.6 vale anche


in presenza di dichiarazioni di procedure e che l’implicazione dell’Eserci-
zio 5.18 vale anche in presenza di chiamate di procedura.
S OLUZIONE . Iniziamo dimostrando un risultato che servirà successiva-
mente nella dimostrazione. Consideriamo la lista ae e dimostriamo che
vale l’implicazione per induzione sulla costruzione della lista, assumendo
il risultato dell’Esercizio 3.6. Per la base il risultato è immediato non es-
sendo possibile nessuna transizione da •. Consideriamo adesso e, ae. Per
ipotesi tale lista è ben formata. Allora per la seguente regola lo sono anche
e ed ae
∆ ⊢I e : τ, ∆ ⊢I ae : aet
(6.1)
∆ ⊢I e, ae : τ, aet
altrimenti non lo sarebbe nemmeno e, ae. Per ciò che riguarda la semantica
dinamica abbiamo due possibilità
→e he′ , σi
ρ ⊢∆ he, σi −
(6.2)
→ae h(e′ , ae), σi
ρ ⊢∆ h(e, ae), σi −

→ae hae′ , σi
ρ ⊢∆ hae, σi −
(6.3)
→ae h(k, ae′ ), σi
ρ ⊢∆ h(k, ae), σi −
Se applichiamo la regola (6.2) dobbiamo dimostrare che e′ , ae è ben for-
mata. Per la regola (6.1) e′ , ae è ben formata se lo sono le sue componenti
ed effettivamante e′ lo è per la dimostrazione dell’Esercizio 3.6 mentre ae
lo è per ipotesi. Se applichiamo la regola (6.3) dobbiamo dimostrare che
k, ae′ è ben formata, ma k è una costante e dunque è sempre ben formata,
mentre ae′ lo è per ipotesi induttiva poiché ae lo è per ipotesi e ae′ è otte-
nuta da ae mediante l’applicazione di una regola dinamica.
Eseguiamo ora la dimostrazione per la dichiarazione di procedura conside-
rando d = procedure p(f orm)c. Per ipotesi essa è ben formata e l’unica
transizione dinamica possibile è

→d h[p = λf orm.c′ ], σi
ρ ⊢∆ hprocedure p(f orm)c, σi −
6.2. ESERCIZI 115

che porta ad una configurazione terminale hρ, σi dove l’ambiente dinamico


è sempre ben formato.
Consideriamo d = (f orm = ae), con d ben formata. Allora per la regola
f orm : ∆0 , ∆ ⊢I ae : T (f orm)
(6.4)
∆ ⊢I f orm = ae : ∆0
lo sono anche le liste ae e f orm, alla quale riusciamo ad associare l’am-
biente ∆0 (in seguito, per semplicità, quando riusciamo ad associare un
ambiente statico alla lista f orm diremo che f orm è ben formata). L’unica
regola dinamica applicabile è la seguente
→ae hae′ , σi
ρ ⊢∆ hae, σi −
→d hf orm = ae′ , σi
ρ ⊢∆ hf orm = ae, σi −
Dobbiamo ora dimostrare che f orm = ae′ è una dichiarazione ben forma-
ta e questo vale, per la regola (6.4), se lo sono f orm e ae′ . Ma f orm lo
è per ipotesi perché non è stata alterata, mentre ae′ lo è in base alla dimo-
strazione fatta all’inizio.
Consideriamo infine l’unico comando che interessa le procedure: la chia-
mata. Consideriamo c = p(ae). Per ipotesi c è ben formato e quindi
∆ ⊢I ae : aet
, ∆(p) = aetproc
∆ ⊢I p(ae)
Dunque ae è ben formato e inoltre p ∈ Dom(∆). Quest’ultimo fatto
implica che la dichiarazione di p è ben formata, perciò devono valere le
premesse della regola
f orm : ∆0 , ∆[∆0 ] ⊢I∪I0 c
(6.5)
∆ ⊢I procedure p(f orm)c : [p = T (f orm)proc]
Quindi riusciamo ad associare l’ambiente ∆0 alla lista f orm e c, corpo del-
la procedura, è ben formato nell’ambiente ∆[∆0 ]. Vediamo adesso l’unica
regola della semantica dinamica che possiamo applicare

→c hf orm = ae; c′ , σi, ρ(p) = λf orm.c′


ρ ⊢∆ hp(ae), σi −
116 CAPITOLO 6. PROCEDURE

Dobbiamo quindi dimostrare che il comando f orm = ae; c′ è ben formato,


ed esso lo è se lo sono i suoi componenti. Ma f orm = ae è ben formata
grazie alla regola 6.4 perché per ipotesi ae è ben formata e a f orm riu-
sciamo ad associare un ambiente. Per ciò che riguarda c′ , sia nel caso di
scoping statico che di scoping dinamico, esso è ben formato se lo è il corpo
della procedura c, in quanto l’eventuale ambiente dinamico è sempre ben
formato. Il corpo della procedura è ben formato se è verificata la seguente
f orm : ∆0 , ∆[∆0 ] ⊢I∪I0 c
, ∆0 : I0
∆ ⊢I c
che è vera visto che le premesse sono le stesse della regola (6.5), supposta
vera per ipotesi. 2

Esercizio 6.6. Dimostrare che l’implicazione dell’Esercizio 4.7 vale an-


che in presenza di dichiarazioni di procedure e che quella dell’Esercizio
5.19 vale anche in presenza di chiamate di procedura.
S OLUZIONE . Assumendo le dimostrazioni degli Esercizi 3.7, 4.7 e 5.19
eseguiamo la dimostrazione analoga cioè dimostriamo la seguente impli-
cazione

x0 ∈ F I(d) ∧ x0 6∈ Dom(∆) ⇒ ∆ 6⊢I d

Consideriamo adesso d = (f orm = ae) e supponiamo x0 ∈ F I(f orm =


ae) = F I(ae). Per definizione l’insieme F I(ae) è l’unione di tutti gli
identificatori liberi delle espressioni facenti parte della lista ae. Dunque il
fatto che x0 ∈ F I(ae) implica che esiste almeno una e contenuta in ae tale
che x0 ∈ F I(e). Allora per la dimostrazione dell’Esercizio 3.7 si ha che
∆ 6⊢I e, dunque ae non è ben formata e di conseguenza non lo è nemmeno
d poiché non riusciamo ad applicare la regola
f orm : ∆0 , ∆ ⊢I ae : aet
∆ ⊢I f orm = ae : ∆0
in quanto una delle premesse è falsa.
Consideriamo ora la dichiarazione d = procedure p(f orm)c con x0 ∈
6.2. ESERCIZI 117

F I(procedure p(f orm)c) = F I(c) \ DI(f orm). Poiché ∆[∆0 ] è ot-


tenuto considerando ∆0 l’ambiente generato da f orm, le condizioni x0 6∈
DI(f orm) e x0 6∈ Dom(∆) implicano che x0 6∈ Dom(∆[∆0 ]). Perciò
vale il risultato dimostrato nell’Esercizio 5.19 e dunque ∆[∆0 ] 6⊢I∪I0 c
implicando la non ben formatezza della dichiarazione per la regola

f orm : ∆0 , ∆[∆0 ] ⊢I∪I0 c


∆ ⊢I procedure p(f orm)c : [p = T (f orm)proc]

Consideriamo infine il comando c = p(ae) e dimostriamo per induzione

x0 ∈ F I(c) \ Dom(∆) ⇒ ∆ 6⊢I c

Supponiamo quindi che x0 ∈ F I(c) = F I(ae) ∪ {p}. Se x0 = p la tesi è


immediata per la regola

∆ ⊢I ae : aet
, ∆(p) = aetproc (6.6)
∆ ⊢I p(ae)

poiché ∆(p) non è definito e dunque p(ae) non può essere ben formato. Se
x0 ∈ F I(ae) per i ragionamenti fatti in precedenza ∆ 6⊢I ae : aet perciò
∆ 6⊢I p(ae) essendo la premessa della regola (6.6) falsa. 2
118 CAPITOLO 6. PROCEDURE
Capitolo 7

Il paradigma funzionale

In questo capitolo introduciamo il paradigma di programmazione funzio-


nale. Al contrario dei linguaggi imperativi visti in precedenza in cui il
passo elementare del calcolo è descritto da assegnamenti, nei linguaggi
funzionali il passo elementare di computazione viene espresso con l’appli-
cazione di funzione. L’idea intuitiva è quella di rendere la programmazione
una attività più vicina alla definizione di funzioni matematiche.
In generale una funzione è una particolare relazione che ad ogni va-
lore del dominio di definizione associa al più un valore del codominio.
Intuitivamente una funzione è una regola di trasformazione degli elementi
del dominio in quelli del codominio. L’applicazione di una funzione ad
un elemento del dominio restituisce come risultato della sua valutazione il
corrispondente valore del codominio ed è quindi l’esecuzione della regola
di trasformazione.
Il concetto di memoria per descrivere le modifiche effettuate dagli as-
segnamenti diventa superfluo nel paradigma funzionale, poiché come ab-
biamo visto il passo elementare di calcolo restituisce un valore (il risul-
tato della funzione). Questa è la differenza fondamentale tra linguaggi
imperativi e linguaggi funzionali.
Nelle prossime sezioni richiameremo prima i fondamenti del paradig-
ma funzionale descrivendo la semantica statica e dinamica del λ-calcolo e
poi riporteremo la soluzione di alcuni esercizi proposti.

119
120 CAPITOLO 7. IL PARADIGMA FUNZIONALE

7.1 Il λ-calcolo
Il λ-calcolo fu sviluppato da Church negli anni ‘30 come base notazionale
per descrivere le funzioni matematiche. Successivamente Kleene dimostrò
che il λ-calcolo è un sistema di calcolo universale come la macchina di
Turing. Infine, McCarthy implementò negli anni ‘50 il vero linguaggio di
programmazione LISP sulla base del λ-calcolo. Adesso esistono nume-
rosi linguaggi funzionali che hanno come nucleo il λ-calcolo. Tra questi
ricordiamo tutti i linguaggi della famiglia ML, Haskell, Scheme.
La sintassi del λ-calcolo è molto semplice e comprende soltanto la
categoria sintattica delle espressioni. Infatti un programma scritto in un
linguaggio funzionale può essere visto come una funzione scritta median-
te composizione di funzioni più semplici applicata ad alcuni parametri di
ingresso. Il risultato della applicazione è il risultato dell’esecuzione del
programma.
L’unico insieme sintattico di base è quello delle

VARIABILI. Var con metavariabili x, y, z, . . ..

e l’unica categoria sintattica derivata è quella delle

E SPRESSIONI. Exp con metavariabile e definita come

e ::= x | (ee) | (λ x.e)

La concatenazione ee di due λ-termini è chiamata applicazione ed il co-


strutto λ x.e è chiamato astrazione. Il significato intuitivo dell’applicazio-
ne e′ e′′ è il passaggio del parametro attuale e′′ alla funzione e′ . Il significa-
to intuitivo dell’astrazione è quello della definizione di una funzione con
parametro formale x e corpo e.
Per semplificare la scrittura dei λ-termini assumeremo nel seguito che
l’applicazione associa a sinistra ed ha precedenza sull’astrazione. Inoltre
assumiamo che il campo di azione della dichiarazione λ.x si estenda a
destra quanto più possibile.
7.2. SEMANTICA STATICA 121

7.2 Semantica statica


Come abbiamo fatto per i linguaggi imperativi, definiamo un sistema for-
male di assiomi e regole di inferenza che permettono di determinare se un
λ-termine è corretto rispetto ad una qualche nozione di tipo che sia rilevan-
te per i linguaggi funzionali. Prima però definiamo quali sono le variabili
libere e legate dei λ-termini. Nel seguito di questo capitolo useremo la
parola termine per riferirci ai λ-termini.
Definizione 7.1 (variabili libere). L’insieme F V (e) di tutte le variabili li-
bere di un termine e è definito per induzione strutturale sulla sintassi dei
termini come
F V (x) = {x}
F V (λx . e) = F V (e) \ {x}
F V (ee′ ) = F V (e) ∪ F V (e′ )
Una variabile del termine e che non è libera si dice legata, e l’occorrenza
della variabile legata x in λx . e si chiama occorrenza di legame.
Per controllare la correttezza dei tipi dei termini associamo nella sin-
tassi del λ-calcolo un tipo a tutte le occorrenze di legame delle variabi-
li. Quindi modifichiamo la sintassi del calcolo sostituendo l’astrazione
con λx : τ . e che ricopre il ruolo delle dichiarazioni dei linguaggi impe-
rativi. Dobbiamo inoltre definire l’insieme dei tipi che ci occorrono per
determinare il tipo di ciascun termine che vogliamo considerare corretto.
Intuitivamente un’astrazione è come la dichiarazione di una procedu-
ra con parametro formale x e corpo costituito da una espressione anziché
da un comando. Infatti nei linguaggi imperativi l’astrazione con corpo
costituito da comandi è il valore denotabile che assegnamo nell’ambien-
te dinamico agli identificatori di procedura. Nel λ-calcolo la valutazio-
ne dell’astrazione deve restituire un valore poiché fa parte della categoria
sintattica delle espressioni. Quindi l’astrazione qui è come una funzione
matematica che prende certi argomenti in ingresso e restituisce dei valori.
Pertanto decidiamo di assegnare alle astrazioni lo stesso tipo delle funzioni
matematiche τ0 → τ1 , dove τ0 è il dominio della funzione (e quindi un tipo
in quanto insieme di valori) e τ1 è il codominio. Possiamo a questo punto
definire i tipi che utilizziamo come
122 CAPITOLO 7. IL PARADIGMA FUNZIONALE

T IPI. Typ con metavariabile τ definita in stile BNF da


τ ::= int | bool | τ → τ
dove → è chiamato costruttore di tipi.
Le regole della semantica statica che assegnano i tipi ai termini sono
riportate in Tab. 7.1. Il simbolo ∆ rappresenta un ambiente statico in cui
le variabili sono legate ai loro tipi ed è definito come per i linguaggi im-
perativi. La prima regola assegna ad una variabile il tipo che questa ha
associato nell’ambiente statico corrente ∆. La seconda regola ci dice che
se il tipo del parametro formale di una astrazione è τ ed il tipo del cor-
po della astrazione è τ ′ , allora il tipo dell’astrazione è il tipo funzionale
τ → τ ′ . Infine l’applicazione ha un tipo corretto se il componente sinistro
è una astrazione ed il componente destro ha un tipo uguale al parametro
formale dell’astrazione. In questo caso il tipo risultante dall’applicazione
è il codominio dell’astrazione.

∆[x : τ ] ⊢ e : τ ′
∆ ⊢ x : ∆(x)
∆ ⊢ λx : τ.e : τ → τ ′

∆ ⊢ e0 : τ → τ ′ , ∆ ⊢ e1 : τ
∆ ⊢ e0 e1 : τ ′

Tabella 7.1: Semantica statica del λ-calcolo.

7.3 Semantica dinamica


Per descrivere il comportamento dinamico dei termini dobbiamo per prima
cosa introdurre il concetto di sostituzione.
Definizione 7.2 (sostituzione). Una sostituzione è una funzione
σ : Var → e.
7.3. SEMANTICA DINAMICA 123

Scriveremo {e1 /x1 , . . . , en /xn }, se σ(xi ) = ei per ogni i ∈ [1, n] (e


σ(xj ) = xj , j 6∈ [1, n]). Scriveremo anche e{e′ /x} intendendo la sosti-
tuzione di e′ al posto di tutte le occorrenze libere di x in e.
La sostituzione e{e′ /x} è valida se soddisfa BV (e) ∩ F V (e′ ) = ∅.
La condizione sulla validità delle sostituzioni è necessaria per evitare il
fenomeno della cattura delle variabili che consiste nel trasformare variabili
libere in variabili legate come effetto indesiderato di una sostituzione. Nel
seguito quando scriviamo una sostituzione assumiamo implicitamente che
sia valida.
La semantica dinamica del λ-calcolo è definita in due passi. Prima
individuiamo le mosse elementari che possono essere eseguite da alcuni
sottotermini e le chiamiamo conversioni. Dopo utilizziamo le conversioni
come gli assiomi di un sistema di regole che definisce le riduzioni che
consentono di effettuare le conversioni in tutti i contesti sintattici. Sono
quindi le riduzioni che definiscono le transizioni dei λ-termini. Nel seguito
chiameremo redex (espressione riducibile) un sottotermine di un λ-termine
a cui possono essere applicate delle conversioni.
Le conversioni del calcolo sono tre e furono introdotte da Curry che le
chiamò α, β e η. Esse sono riportate in Tab. 7.2.

α
α : λx.e −
→ λy.e{y/x}, y 6∈ FV(e)

β
β : (λx.e)e′ −
→ e{e′ /x}
η
η : λx.(ex) −
→ e, x 6∈ FV(e)

Tabella 7.2: Regole di conversione per il λ-calcolo.

La prima regola implementa il cambio di nome delle variabili legate.


Il significato semantico dei termini non è influenzato da questa regola che
tecnicamente cambia il nome del parametro formale di una astrazione e di
tutte le sue occorrenze nel corpo. La seconda regola è quella che imple-
menta il passo di calcolo vero e proprio mediante l’istanziazione del para-
metro formale x con il parametro attuale e′ . Il meccanismo di passaggio
124 CAPITOLO 7. IL PARADIGMA FUNZIONALE

del parametro è basato sulla sostituzione dell’attuale non valutato a tutte


le occorrenze del formale (passaggio per nome). La regola β implementa
quindi la riscrittura di un termine in un altro rappresentando l’applicazione
di una funzione al suo argomento. La regola η implementa l’equivalenza
estensionale delle funzioni. Questa ci dice che due funzioni sono equiva-
lenti se restituiscono gli stessi risultati su tutti i possibili argomenti. Infatti
sia la parte sinistra che quella destra della conversione una volta appli-
cati ad un termine e′ restituiscono ee′ poiché il parametro formale x non
occorre libero in e.
Il secondo passo nella definizione della semantica dinamica del λ-
calcolo serve a permetterci di effettuare conversioni su sottotermini di ter-
mini generici. Inoltre, poiché siamo interessati al risultato della valutazio-
ne di un’espressione e non ai passi elementari del calcolo, astraiamo dalle
particolari conversioni usate definendo una nuova relazione di transizione
non etichettata in termini dei tre assiomi α, β e η. La nuova relazione di
transizione si chiama riduzione ed è definita dalle regole in Tab. 7.3.

α β η
→ e′
e− → e′
e− → e′
e−
→ e′
e− → e′
e− → e′
e−

→ e′
e− → e′′
e− e′ −
→ e′′
→ λx.e′
λx.e − ee′ −
→ e′′ e′ ee′ −
→ ee′′

Tabella 7.3: Regole di riduzione per il λ-calcolo.

Il risultato della valutazione di un λ-termine mediante la relazione di


riduzione è ancora un λ-termine al quale non è più possibile applicare ridu-
zioni, eccetto, eventualmente, per α-conversioni. Infatti queste ultime non
modificano il contenuto informativo di un termine e quindi non producono
passi di calcolo effettivi. I termini che verificano questa proprietà sono det-
ti in forma normale. Quindi il sistema di transizione del λ-calcolo ha come
configurazioni i λ-termini e come relazione di transizione le riduzioni. Le
configurazioni terminali sono i λ-termini in forma normale.
7.3. SEMANTICA DINAMICA 125

Esistono dei λ-termini per cui la scelta dei redex cui applicare le ridu-
zioni puó non condurre ad una forma normale anche se questa esiste. Una
strategia di riduzione che ci consente di raggiungere sempre una forma nor-
male se esiste è detta strategia normalizzante. Una di queste è la strategia
lo (leftmost-outermost) che impone di ridurre sempre il redex più a sinistra
e più esterno del termine (cioé non contenuto in nessun altro redex).
Riportiamo adesso uno dei principali risultati del λ-calcolo che ci assi-
cura la confluenza del calcolo. Essa ci dice che se da un termine è possibile
raggiungere attraverso una sequenza di β ed η riduzioni due termini distin-
ti e′ ed e′′ , allora esiste un terzo termine raggiungibile sia da e′ che da e′′ .
Formalmente abbiamo il seguente teorema.
Teorema 7.3 (Church-Rosser). Per tutti i λ-termini e, e′ ed e′′ tali che
e−→∗ e′ e e −
→∗ e′′ esiste un λ-termine e′′′ tale che e′ −
→∗ e′′′ e e′′ −
→∗ e′′′ ,

dove −
→ è la chiusura riflessiva e transitiva della relazione di riduzione
senza α-conversione.
La relazione di riduzione è utilizzata anche per definire una relazio-
ne di equivalenza, detta di β-uguaglianza, che è utilizzata per controllare
l’uguaglianza semantica di due termini. La β-uguaglianza è definita dalle
regole in Tab. 7.4. Le prime tre regole definiscono le proprietà riflessi-
va, simmetrica e transitiva dell’uguaglianza. La quarta regola dice poi che
le riduzioni mantengono la β-uguaglianza. Infine abbiamo le regole di
congruenza della β-uguaglianza rispetto ad astrazione ed applicazione.

e = e′ e = e′ , e′ = e′′ → e′
e−
e=e
e′ = e e = e′′ e = e′

e = e′ e0 = e′0 e1 = e′1
λx.e = λx.e′ e0 e1 = e′0 e1 e0 e1 = e0 e′1

Tabella 7.4: β-uguaglianza.

La β-uguaglianza può direttamente essere definita in termini di ridu-


zioni.
126 CAPITOLO 7. IL PARADIGMA FUNZIONALE

Definizione 7.4. Due λ-termini e ed e′ sono β-uguali (e = e′ ) se e soltanto


se esistono e0 , . . . , en (n ≥ 0) tali che e0 ≡ e, en ≡ e′ e

∀i < n, (ei −
→ ei+1 ∨ ei+1 −
→ ei ).

Concludiamo questa sezione trattando funzioni con più di un para-


metro formale. L’idea è quella di assumere che il risultato dell’applica-
zione di una funzione è a sua volta una funzione. In questo modo pos-
siamo utilizzare funzionali per rappresentare funzioni con più argomenti.
Quindi possiamo scrivere in λ-notazione una funzione f (x, y) = e con
FV(e) = {x, y} come λx.(λy.e). Per calcolare f (e0 , e1 ) abbiamo bisogno
di due β-riduzioni
β β
((λx.(λy.e))e0 )e1 −
→ (λy.e{e0 /x})e1 −
→ (e{e0 /x}){e1 /y}.

Questa tecnica prende il nome di carrizzazione di funzioni. Per non appe-


santire la notazione scriveremo nel seguito λx1 . . . xn .e intendendo
λx1 .(λx2 . . . (λxn .e) . . .).

7.4 Esercizi
Esercizio 7.1. Definire una funzione BV : e → V ar che determina le
variabili legate di un termine e.
S OLUZIONE . Procediamo per induzione strutturale sulla sintassi dei ter-
mini, tenendo conto che l’insieme ottenuto dall’applicazione di BV deve
essere il complemento rispetto all’insieme determinato da F V per ogni
occorrenza di ogni variabile in e

BV (x) = ∅
BV (λx . e) = BV (e) ∪ {x}
BV (ee′ ) = BV (e) ∪ BV (e′ )

Esercizio 7.2. Sia e un λ-termine tale che F V (e) 6= ∅. Dimostrare che


∀∆ : V . F V (e) 6⊆ V abbiamo ∆6⊢V e : τ .
7.4. ESERCIZI 127

S OLUZIONE . Dimostriamo questo risultato per induzione sulla struttura


della categoria delle espressioni.

BASE
Consideriamo e = x e supponiamo che F I(e) = {x} 6⊆ V , cioè x 6∈ V .
Sappiamo che ad un tale termine possiamo assegnare un tipo se vale la
seguente regola

∆ ⊢V x : ∆(x)

però per ipotesi x non appartiene al dominio di ∆ e quindi non possiamo


determinare il tipo ∆(x) da cui ∆ 6⊢V e : τ .

PASSO I NDUTTIVO
Supponiamo di poter applicare l’ipotesi induttiva ai termini e0 ed e1 . Con-
sideriamo e = e0 e1 e supponiamo che F I(e) 6⊆ V . Questo significa che,
per definizione di F I(e0 e1 ), F I(e0 ) ∪ F I(e1 ) 6⊆ V cioè,

F I(e0 ) 6⊆ V ∨ F I(e1 ) 6⊆ V

Possiamo quindi concludere che ad e non riusciamo ad assegnare un tipo


in quanto almeno una delle premesse della seguente regola è falsa
∆ ⊢V e0 : τ1 −
→ τ0 , ∆ ⊢V e1 : τ1
∆ ⊢V e0 e1 : τ0
Consideriamo infine e = λx.e0 e supponiamo F I(e) = F I(e0 )\{x} 6⊆ V ,
questo implica che F I(e0 ) 6⊆ V ∪ {x}. Quindi considerando l’ambiente
statico ∆[x : τ ] e il suo dominio V ∪ {x}, possiamo applicare l’ipotesi
induttiva ad e0 , cioè ∆[x : τ ] 6⊢V ∪{x} e0 : τ0 . Possiamo perciò concludere
che non riusciamo ad assegnare un tipo al termine e perché la premessa
della seguente regola è falsa
∆[x : τ ] ⊢V ∪{x} e0 : τ0
∆ ⊢V λx : τ.e0 : τ −
→ τ0
2
128 CAPITOLO 7. IL PARADIGMA FUNZIONALE

Esercizio 7.3. Dimostrare che la sostituzione definita per induzione strut-


turale sulla sintassi dei termini da
 ′
′ e se x ≡ y
y{e /x} =
y altrimenti
(e0 e1 ){e′ /x} = e0 {e′ /x}e1 {e′ /x}

 λy.e0 if x ≡ y
′ ′
(λy.e0 ){e /x} = λy.(e 0 {e /x}) se x 6≡ y e y 6∈ FV(e′ )
λw.(e0 {w/y}{e′/x}) se x 6≡ y e y ∈ FV(e′ )

dove w 6∈ FV(e0 ∪ e′ ) è sempre valida.


S OLUZIONE . Ricordiamo inizialmente che una sostituzione e{e′ /x}è va-
lida se BV (e) ∩ F V (e′ ) = ∅. Dunque dimostriamo che la sostituzione
è valida per induzione sulla struttura delle espressioni verificando ad ogni
passo che è valida la precedente condizione.

BASE
Sia e = y allora BV (e) = ∅, dunque ∀e′ . BV (e) ∩ F V (e′ ) = ∅, cioè la
sostituzione è sempre valida.

PASSO I NDUTTIVO
Sia e = e0 e1 e supponiamo che, per ipotesi induttiva, la sostituzione in e0
ed e1 sia valida, cioè BV (e0 ) ∩ F V (e′ ) = ∅ e BV (e1 ) ∩ F V (e′ ) = ∅.
Quindi sapendo che BV (e) = BV (e0 ) ∪ BV (e1 ), vediamo a cosa è uguale
BV (e) ∩ F V (e′ )

BV (e) ∩ F V (e′ ) = (per definizione di BV (e))


= (BV (e0 ) ∪ BV (e1 )) ∩ F V (e′ ) = (per le proprietà degli insiemi)
= (BV (e0 ) ∩ F V (e′ )) ∪ (BV (e1 ) ∩ F V (e′ )) = (per ip. induttiva)
=∅∪∅=∅

Perciò la sostituzione è valida.


Consideriamo infine e = λy.e0 e supponiamo per ipotesi induttiva che la
sostituzione sia valida su e0 , cioè BV (e0 ) ∩ F V (e′ ) = ∅. Considerando
che per definizione BV (λy.e0 ) = BV (e0 ) ∪ {y} vediamo cosa succede
caso per caso nella sostituzione.
7.4. ESERCIZI 129

Se x ≡ y la sostituzione non viene effettuata dunque il problema non si


pone.
Se x 6≡ y distinguiamo ancora in due casi a seconda che y appartenga o
meno alle variabili libere di e′ . Se y 6∈ F V (e′ ) abbiamo
BV (e) ∩ F V (e′ ) = (per definizione di BV (e))
= (BV (e0 ) ∪ {y}) ∩ F V (e′ ) = (per le proprietà degli insiemi)
= (BV (e0 ) ∩ F V (e′ )) ∪ ({y} ∩ F V (e′ )) = (per ipotesi induttiva)
= ∅ ∪ ({y} ∩ F V (e′ )) = (per ipotesi)
=∅∪∅=∅
Infine se y ∈ F V (e′ ) per effettuare comunque una sostituzione valida vie-
ne applicata una α-conversione al termine λy.e0 . In tal modo la sosti-
tuzione viene eseguita sul termine λw.(e0 {w/y}) semanticamente uguale
ad e ma con la differenza sintattica che ad y abbiamo sostituito w. A
questo punto ricadiamo nel caso precedente in quanto w è stata scelta in
modo che non appartenesse alle variabili libere di e′ e rendesse valida l’α-
conversione su e0 . Poiché e0 {w/y} è un termine valido ed è contenuto in
e possiamo applicare l’ipotesi induttiva e quindi dedurre cha l’intera sosti-
tuzione è valida. 2

Esercizio 7.4. Dato il termine (λ x.(λ y.xy))((λ z.zy)(λ w.w)), ridurlo in


forma normale costruendo il sistema di transizione corrispondente.
S OLUZIONE . Come primo passo possiamo applicare una η-conversione al
sottotermine λ y.xy, ottenendo la derivazione
η
λ y.xy −
→x
λ y.xy −
→x
(λ x.(λ y.xy)) −
→ (λ x.x)
(λ x.(λ y.xy))((λ z.zy)(λ w.w)) −
→ (λ x.x)((λ z.zy)(λ w.w))
Il primo passo di inferenza trasforma la η-conversione in riduzione in base
alla terza regola in Tab. 7.3. Poi applichiamo la regola che consente di
effettuare riduzioni nel contesto di una astrazione. Infine la quinta regola
in Tab. 7.3 ci consente di ultimare la derivazione della transizione.
130 CAPITOLO 7. IL PARADIGMA FUNZIONALE

(λ x.(λ y.xy))((λ z.zy)(λ w.w))


η

e1 e2 e3

η η

e4 e5 e6 e7

(λ w.w)y (λ x.x)y λ r.yr


η

e1 = λ r.((λ z.zy)(λ w.w))r e2 = (λ x.x)((λ z.zy)(λ w.w))


e3 = (λ x.(λ y.xy))((λ w.w)y) e4 = (λ z.zy)(λ w.w)
e5 = λ r.((λ w.w)y)r e6 = (λ x.x)((λ w.w)y)
e7 = (λ x.(λ y.xy))y

Figura 7.1: Sistema di transizione di (λ x.(λ y.xy))((λ z.zy)(λ w.w)).


7.4. ESERCIZI 131

Consideriamo adesso la β-conversione che possiamo applicare all’astra-


zione λ .x più esterna e più a sinistra (notiamo che questo sarebbe il redex
scelto dalla strategia normalizzante lo). La derivazione corrispondente è
β
(λ x.(λ y.xy))((λ z.zy)(λ w.w)) −
→ (λ y.xy){((λ z.zy)(λ w.w))/x}
(λ x.(λ y.xy))((λ z.zy)(λ w.w)) −
→ (λ y.xy){((λ z.zy)(λ w.w))/x}
Poiché la variabile y occorre libera nel termine che dobbiamo sostituire
alla x, in base alla definizione di sostituzione dell’Esercizio 7.3 dobbiamo
ridenominare la y del termine in cui sostituiamo ottenendo

(λ r.((λ z.zy)(λ w.w))r).

In questo modo la sostituzione evita la cattura della variabile libera y. No-


tiamo che la sostituzione cosı̀ definita implementa una α-conversione im-
plicita. Infatti avremmo ottenuto lo stesso risultato effettuando prima una
α-riduzione
α
(λ y.xy) −
→ (λ r.xr)
(λ y.xy) −
→ (λ r.xr)
(λ x.(λ y.xy)) −
→ (λ x.(λ r.xr))
(λ x.(λ y.xy))((λ z.zy)(λ w.w)) −
→ (λ x.(λ r.xr))((λ z.zy)(λ w.w))
e poi la β-riduzione a partire dalla configurazione

(λ x.(λ r.xr))((λ z.zy)(λ w.w)).

La sostituzione della β-riduzione è questa volta valida e può quindi essere


effettuata normalmente. Proseguendo con le riduzioni otteniamo il sistema
di transizione di Fig. 7.1 in cui non abbiamo riportato le α-conversioni. Le
transizioni non etichettate sono β-riduzioni. 2

Esercizio 7.5. Sia e un termine e ∆ un ambiente statico per cui esiste un


tipo τ tale che ∆ ⊢V e : τ . Dimostrare che se
α
→ e′ allora esiste τ ′ tale che α(∆) ⊢α(V ) e′ : τ ′ ;
1. e −
132 CAPITOLO 7. IL PARADIGMA FUNZIONALE

β
→ e′ allora esiste τ ′ tale che ∆ ⊢V e′ : τ ′ ;
2. e −
η
→ e′ allora esiste τ ′ tale che ∆ ⊢V e′ : τ ′ .
3. e −
S OLUZIONE . Innanzitutto, per semplicità, quando ∃τ . ∆ ⊢V e : τ diremo
che il termine e è ben formato. Inoltre, quando possibile, tralasceremo di
specificare in quale ambiente statico il termine è ben formato per evitare di
appesantire inutilmente lo svolgimento dell’esercizio. Supponiamo, quin-
di, che e sia ben formato in ∆ e dimostriamo caso per caso che la relazione
di riduzione non altera la condizione di ben formatezza dei termini a par-
tire dall’ambiente statico originale. Si noti che tutte e tre le dimostrazioni
vanno fatte per induzione sulla struttura delle espressioni ma in tutti i casi
è immediata la dimostrazione per la base in quanto nessuna delle conver-
sioni è applicabile ad una variabile quindi per ogni caso vediamo solo il
passo induttivo.
1. Consideriamo e = λx.e0 . Sapendo che ad e riusciamo ad associare
un tipo, valgono le premesse della seguente regola
∆[x : τ ] ⊢V ∪{x} e0 : τ ′
(7.1)
∆ ⊢V λx : τ.e0 : τ − → τ′
Quindi ad e0 riusciamo ad assegnare un tipo in ∆[x : τ ]. La α-
conversione esegue la seguente transizione
α
λx.e0 −
→ λy.e0 {y/x}
Vogliamo quindi dimostrare che il termine λy.e0{y/x} è ben for-
mato nell’ambiente statico α(∆). Ma questo è vero se vale la re-
gola 7.1 riscritta per λy.e0{y/x} e con ambiente α(∆), cioè è ve-
ro se α(∆)[y : τ ] ⊢α(V )∪{y} e0 {y/x} : τ ′ . Ma effettivamente
α(∆[x : τ ]) = α(∆)[y : τ ], quindi se in ∆[x : τ ] associavamo
ad x il tipo τ , analogamente in α(∆)[y : τ ] associamo il tipo τ a y.
Perciò se e0 è ben formato in ∆[x : τ ] allora e0 {y/x} è ben formato
in α(∆)[y : τ ].
Consideriamo adesso e = e0 e1 . Per ipotesi e è ben formata dunque
vale la seguente regola
∆ ⊢V e0 : τ −→ τ ′ , ∆ ⊢V e1 : τ
(7.2)
∆ ⊢V e0 e1 : τ ′
7.4. ESERCIZI 133

Questo significa che e0 è un’astrazione e che quindi è applicabile la


regola della semantica dinamica
α
→ e′0
e0 −
→ e′0 e1
e0 e1 −

Quindi se e0 = λx.e2 si ha che e′0 = λy.e2 {y/x}. Dimostrare che


e′0 e1 è ben formato consiste, per la regola 7.2 riscritta per e′0 e1 , nel
dimostrare che lo sono le sue componenti. Ma e1 lo è per ipotesi
mentre e′0 lo è per quanto dimostrato in precedenza in quanto per
ipotesi e0 è ben formato.

2. Nel caso in cui e = λx.e0 la β-conversione non è applicabile dunque


l’implicazione è sempre vera.
Consideriamo quindi e = e0 e1 .Per ipotesi è ben formata, cioè va-
le la regola (7.2). Quindi sia e0 che e1 sono ben formate in ∆ e in
particolare e0 è una astrazione. Per questo possiamo applicare la β-
conversione considerando e0 = λx.e′0 (si noti che essendo e0 ben for-
mato per ipotesi, allora lo è anche e′0 per la regola 7.1 nell’ambiente
∆[x : τ ])

β
(λx.e′0 )e1 −
→ e′0 {e1 /x}

A questo punto vogliamo assegnare un tipo a e′0 {e1 /x} in ∆, dove


sia e′0 che e1 sono ben formati per ipotesi. Quindi per induzione sul-
la definizione di sostituzione (vedi Esercizio 7.3) dimostriamo che al
termine ottenuto mediante la sostituzione di un termine ben formato
in un’altro ben formato riusciamo ad assegnare un tipo.

BASE
Consideriamo e′0 = y. Per definizione di sostituzione, se x ≡ y la
sostituzione non avviene e quindi il termine che risulta è lo stesso dal
quale partiamo che è ben formato per ipotesi. Se, invece, x 6≡ y il ter-
mine risultante è y che, essendo una variabile, è sempre ben formato.
134 CAPITOLO 7. IL PARADIGMA FUNZIONALE

PASSO I NDUTTIVO
Sia e′0 = e′ e′′ , dove per l’ipotesi di ben formatezza di e′0 sia a e′ che
ad e′′ riusciamo ad assegnare un tipo. In questo caso si ha per defini-
zione che e′ e′′ {e1 /x} = e′ {e1 /x}e′′ {e1 /x}. Quest’ultimo termine è
ben formato se lo sono le sue componenti, e ciò avviene per ipotesi
induttiva essendo ben formati sia e′ che e′′ .
Consideriamo infine e′0 = λx.e′ (si noti che l’ipotesi che e′0 sia ben
formato implica che lo sia anche e′ ), in tal caso si possono verificare
tre situazioni differenti.
Se x ≡ y allora non ci sono occorrenze libere di x in e′0 e dunque la
sostituzione non avviene lasciando il termine, ben formato per ipo-
tesi, inalterato. Se invece le due variabili sono differenti dobbiamo
ancora distinguere due casi; se y 6∈ F V (e1 ) allora dalla sostituzione
si ottiene il termine λy.e′ {e1 /x} al quale riusciamo ad assegnare un
tipo perché e′ {e1 /x} è ben formato per ipotesi induttiva.
Invece se y ∈ F V (e1 ) il termine che si ottiene λw.(e′ {w/y}{e1/x})
è ben formato se lo è il termine e′ {w/y}{e1/x}. Ma a tale termine
riusciamo ad applicare l’ipotesi induttiva perché e′ {w/y} è ottenuto
da un termine ben formato mediante un’α-conversione e, per quanto
già dimostrato, il termine che si ottiene è ancora ben formato.

3. Consideriamo infine la η-conversione e prendiamo e = λx.(e0 x) (in


altri tipi di astrazione la conversione non è applicabile). Per ipotesi
il termine e è ben formato, cioè vale la regola (7.1) riscritta per e.
Questo significa che vale anche la regola (7.2) per l’applicazione
e0 x nell’ambiente ∆[x : τ ]. Riduciamo allora il termine con la η-
conversione
η
λx.(e0 x) −
→ e0 , x 6∈ F V (e0 )

Vogliamo vedere se e0 è ben formato in ∆ essendolo per ipotesi in


∆[x : τ ], ma poiché x 6∈ F V (e0 ) nella valutazione del tipo di e0 il
tipo di x non viene preso dall’ambiente statico e quindi valutare e0
in ∆ o in ∆[x : τ ] è indifferente.

2
Capitolo 8

Un semplice linguaggio
funzionale

In questo capitolo introduciamo la sintassi e la semantica di un semplice


linguaggio funzionale mostrando come questo sia costituito essenzialmen-
te dal λ-calcolo arricchito con alcuni zuccheri sintattici. Infatti l’intuizione
alla base della programmazione funzionale è quella di vedere un program-
ma come l’implementazione di una relazione tra i valori di ingresso e quel-
li di uscita. Quindi un programma è una funzione (tipicamente ottenuta
per composizione da funzioni più semplici) che abbiamo visto può essere
codificata mediante un λ-termine.
Nei linguaggi funzionali la relazione tra gli ingressi e le uscite è otte-
nuta direttamente mediante i risultati restituiti dalle funzioni. Al contrario,
nei linguaggi imperativi i risultati sono ottenuti indirettamente ispezionan-
do la memoria al termine di una esecuzione. Come conseguenza mostrere-
mo che per descrivere il comportamento dinamico dei linguaggi funzionali
è sufficiente utilizzare gli ambienti dinamici.
Il capitolo è organizzato riportando la sintassi di un semplice linguag-
gio funzionale che chiameremo F UN. Poi discuteremo le differenze prin-
cipali con I MP e forniremo la semantica statica e dinamica di F UN. In
questo caso non abbiamo un capitolo per ogni categoria sintattica rilevante
perché mostriamo solo le differenze rispetto ad I MP e quindi necessitiamo
di minor spazio. Infine riportiamo la soluzione di alcuni esercizi proposti.

135
136 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

8.1 Sintassi
I concetti su cui si fonda il paradigma funzionale sono le espressioni e le
funzioni. In particolare, i linguaggi funzionali puri non contengono co-
mandi e quindi non hanno bisogno di una memoria aggiornabile mediante
assegnamenti.
Qui richiamiamo brevemente la sintassi del linguaggio di programma-
zione F UN utilizzando la notazione BNF. Gli insiemi sintattici di base coin-
cidono con quelli di I MP (vedi Sezione 2.2) con la sola eccezione che gli
identificatori Id sono sostituiti dalle variabili Var con metavariabile x (vedi
Sezione 8.2).
Le categorie sintattiche delle costanti, dei parametri attuali e degli at-
tuali valutati coincidono con quelle di I MP cosı̀ come i valori ed i tipi
esprimibili. Non avendo in F UN le memorie, le categorie sintattiche dei
comandi, dei valori e dei tipi memorizzabili non sono presenti. Le altre
categorie sintattiche derivate di F UN sono

E SPRESSIONI. Exp con metavariabile e definite come

e ::= k | x | e bop e′ | uop e | let d into e |


if e then e′ else e′′ | f (ae)

D EFINIZIONI. Def con metavariabile d definite come

d ::= nil | x : τ = e | d; d | d and d | d in d | rec d |


f orm = ae | function f (f orm) : τ = e | ρ

F ORMALI. Form con metavariabile f orm definiti come

f orm ::= • | x : τ, f orm

C HIUSURE . Abs con metavariabile abs definite come

abs ::= λ f orm.e : τ


8.1. SINTASSI 137

VALORI DENOTABILI. DVal = bool ∪ int ∪ Abs con metavariabile


dv definiti come

dv ::= k | abs

T IPI DENOTABILI. DTyp = Typ ∪ {ATyp → Typ} con metavaria-


bile dt definiti come

dt ::= τ | aet → τ

T IPI DEGLI ATTUALI . ATyp con metavariabile aet definiti come

aet ::= • | dt, aet

Rispetto alle espressioni di I MP abbiamo aggiunto in F UN il blocco per le


espressioni, l’espressione condizionale e la chiamata di funzione. Essendo
la funzione un’astrazione di espressioni, la sua chiamata cade in questa
categoria cosı̀ come la chiamata di procedura (astrazione di comandi) è
nella categoria sintattica Com.
Le definizioni sostituiscono le dichiarazioni di I MP poiché non avendo
più memoria è superfluo distinguere tra const e var. In questo contesto
è come se tutte le variabili fossero const e quindi evitiamo di premettere
qualunque parola chiave alla variabile x. L’altra novità è la definizione
ricorsiva rec d. La peculiarità di questa dichiarazione è che le variabili
che si stanno definendo in d vengono considerate come già presenti nel-
l’ambiente in cui si elabora d consentendo di definire variabili in termini di
se stesse. Infine la definizione di funzione sostituisce poi la dichiarazione
di procedura. Per le funzioni il corpo è un’espressione ed inoltre bisogna
esplicitare il tipo del codominio nella definizione.
I formali riflettono le stesse modifiche fatte per le definizioni semplici
e quindi non distinguiamo tra const e var. Inoltre le astrazioni ades-
so hanno come corpo una espressione e non un comando. Per simmetria
con la definizione di funzione, nell’astrazione si registra anche il tipo del
codominio.
Dai valori denotabili è scomparsa la locazione l e corrispondentemente
τ loc dai tipi denotabili. In questi ultimi il tipo funzionale aet → τ sosti-
tuisce il tipo aetproc che era necessario per gli identificatori di procedura.
138 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

Per concludere, i tipi degli attuali sono costituiti ora a partire dalla base
dt anziché et perché prevediamo subito di poter passare funzioni come
parametri di altre funzioni.

8.2 Confronto con I MP


Per confrontare I MP e F UN cerchiamo prima di individuare le origini del-
le caratteristiche fondamentali dei linguaggi imperativi. Questi sono stati
progettati e realizzati come astrazioni di architetture convenzionali la cui
componente fondamentale è la memoria. Da qui il concetto di identificato-
re e locazione come indirizzo di una cella di memoria. Questo rende molto
diverso un identificatore di un linguaggio imperativo da una variabile di
una formula matematica. Infatti gli identificatori (o meglio i valori delle
celle di memoria che identificano) possono essere modificati mediante as-
segnamenti, mentre le variabili no. Una variabile rappresenta una quantità
fissata, anche se non nota.
L’altro aspetto fondamentale dei linguaggi imperativi è legato all’itera-
zione di sequenze di comandi. L’iterazione deriva da una astrazione del ci-
clo di fetch-execute delle architetture tradizionali. Nella definizione di fun-
zioni matematiche la ripetizione di trasformazioni si implementa mediante
il concetto di ricorsione (rec d nella sintassi di F UN).
Il paradigma funzionale vuole essere una implementazione delle fun-
zioni matematiche e quindi si basa sul concetto di variabile (e non identifi-
catore e locazione) e di definizione ricorsiva (non iterazione). Non avendo
gli identificatori e le locazioni, e quindi di conseguenza la memoria, pos-
siamo definire la macchina astratta di F UN servendoci esclusivamente degli
ambienti dinamici che associano variabili e valori (dobbiamo solo sostitui-
re l’insieme degli identificatori con quello della variabili nella definizione
di ρ data per i linguaggi imperativi).
Utilizzando il meccanismo della ricorsione per astrarre passi ripetuti
del calcolo, i costrutti di controllo della sequenza tipo while o for sono
superflui. Anche la composizione sequenziale non serve in quanto rea-
lizzata mediante composizione di funzioni. Ne consegue che la categoria
sintattica dei comandi è superflua nei linguaggi funzionali.
8.3. SEMANTICA DI FUN 139

Infine sostituiamo le procedure di I MP con le funzioni. Esse hanno


un’espressione come corpo e quindi la loro valutazione restituisce un va-
lore il cui tipo deve essere esplicitato nella dichiarazione della funzione. I
meccanismi di passaggio dei parametri sono analoghi a quelli di I MP.

8.3 Semantica di F UN
Come abbiamo fatto per I MP e per il λ-calcolo, cominciamo con la defini-
zione delle variabili libere e legate delle espressioni di F UN.

Definizione 8.1 (variabili libere nelle espressioni). La funzione

F V : Exp → V ar

che ad ogni espressione associa l’insieme delle sue variabili libere è defi-
nita per induzione da

F V (k) = ∅
F V (x) = {x}
F V (e0 bop e1 ) = F V (e0 ) ∪ F V (e1 )
F V (uop e) = F V (e)
F V (let d into e) = F V (d) ∪ (F V (e) \ BV (d))
F V (if e then e0 else e1 ) = F V (e) ∪ F V (e0 ) ∪ F V (e1 )
F V (f (ae)) = {f } ∪ F V (ae)

Per determinare completamente la funzione F V definita sulle espres-


sioni, dobbiamo definire anche le variabili libere che compaiono nelle
definizioni.

Definizione 8.2 (variabili libere nelle definizioni). La funzione

F V : Def → V ar
140 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

che ad ogni definizione associa l’insieme delle sue variabili libere è defi-
nita per induzione da
F V (nil) = ∅
F V (x : τ = e) = F V (e)
F V (d0 ; d1) = F V (d0 in d1 ) = F V (d0 ) ∪ (F V (d1 ) \ BV (d0 ))
F V (d0 and d1 ) = F V (d0 ) ∪ F V (d1 )
F V (rec d) = F V (d) \ BV (d)
F V (f orm = ae) = F V (ae)
F V (function f (f orm) : τ = e) = F V (e) \ BV (f orm)
[
F V (ρ) = F V (ρ(x))
x∈Dom(ρ)

Notiamo che qui gli ambienti possono avere variabili libere (vedi nel
seguito la discussione sulla semantica dinamica della ricorsione e delle
funzioni). Poiché queste sono definite puntualmente, dobbiamo definire
F V anche sui valori denotabili che sono legati alle variabili negli ambienti.
Definizione 8.3 (variabili libere dei valori denotabili). La funzione
F V : Dval → V ar
che ad ogni valore denotabile associa l’insieme delle sue variabili libere è
definita per induzione da
F V (k) = ∅ F V (λ f orm.e : τ ) = F V (e) \ BV (f orm)
Infine dobbiamo definire F V anche per i parametri formali ed attuali.
Definizione 8.4 (variabili libere nei parametri). Le funzioni
F V : F orm → V ar e F V : AExp → V ar
che ad ogni parametro formale ed attuale rispettivamente associano l’in-
sieme delle loro variabili libere sono definite per induzione da
F ORMALI ATTUALI
F V (•) = ∅ F V (•) = ∅
F V (f orm) = ∅ F V (e, ae) = F V (e) ∪ F V (ae)
8.3. SEMANTICA DI FUN 141

⊢V n : int ⊢V t : bool ⊢V nil : ∅ ⊢V (x : τ = e) : [x = τ ]

⊢V d0 : ∆0 , ⊢V d1 : ∆1 ⊢V d0 : ∆0 , ⊢V d1 : ∆1
⊢V d0 ; d1 : ∆0 [∆1 ] ⊢V d0 and d1 : ∆0 , ∆1

⊢V d1 : ∆1 ⊢V d : ∆′
⊢V d0 in d1 : ∆1 ⊢V rec d : ∆′

form : ∆0 ⊢V ρ(x) : ∆(x)


, ∀x ∈ W, ρ : W, ∆ : W
⊢V form = ae : ∆0 ⊢V ρ : ∆
Tabella 8.1: Semantica statica per le definizioni di F UN:ambienti.

8.3.1 Semantica statica


A questo punto possiamo definire la semantica statica dei costrutti di F UN.
Dal punto di vista statico abbiamo ancora bisogno di un ambiente statico
∆ definito come per I MP che associa le variabili dei termini con i loro tipi.
Le regole della semantica statica per le espressioni sono le stesse del-
le corrispondenti espressioni per I MP. Per il blocco la semantica statica è
definita dalla regola introdotta nell’Esercizio 4.5. La regola per l’espres-
sione condizionale si trova nell’Esercizio 3.5. Per la chiamata di funzione
dobbiamo controllare che il tipo dei parametri attuali coincida con quello
dei formali registrato al momento della definizione della funzione nell’am-
biente statico e che anche il risultato della funzione sia corretto. La rego-
la che effettua questi controlli è la terza in Tab. 8.3 (abbiamo raccolta in
un’unica tabella le regole della semantica statica delle funzioni per omo-
geneità con la presentazione delle procedure). Per poter applicare la regola
abbiamo bisogno di determinare il tipo degli attuali ae. A questo scopo
utilizziamo il predicato definito dall’ultimo gruppo di regole in Tab. 8.3.
Cominciamo con la definizione ricorsiva che consente di definire va-
riabili e funzioni in termini di loro stesse. Quindi per determinare il tipo
delle variabili e funzioni coinvolte dobbiamo assumere di avere già a di-
142 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

sposizione l’ambiente statico generato dall’elaborazione della definizione


ricorsiva. In realtà è sufficiente considerare il sottoinsieme dell’ambiente
che assegna i tipi alle variabili e funzioni definite in d. A questo scopo una
possibile regola è

∆[∆′|V0 ] ⊢V ∪V0 d : ∆′
, V0 = F V (d) ∩ BV (d)
∆ ⊢V rec d : ∆0
Questa regola è però di difficile applicazione poiché avendo a disposizio-
ne l’ambiente statico corrente ∆ e la definizione ricorsiva d da elaborare,
dobbiamo indovinare quale sia ∆′ per soddisfare la premessa. Cerchiamo
di capire a questo punto quale sia il ruolo dell’ambiente statico corrente
nell’elaborazione di una definizione.
Per ogni tipo di definizione, il dominio dell’ambiente definito da d è
determinato dalla sintassi di d e coincide con BV (d). L’insieme dei tipi
da associare alle variabili o funzioni definite in d è espressamente scritto
nella sintassi e quindi anche questi possono essere determinati a partire
soltanto da d. Notiamo infatti che assegnamo un tipo alle variabili con le
definizioni semplici e le definizioni di funzioni. In questo modo possiamo
determinare il ∆′ generato da d senza utilizzare ∆. Il problema che si può
però presentare riguarda la correttezza della definizione. Infatti il tipo di
e nelle definizioni semplici o nelle definizioni di funzioni può non coinci-
dere con quello esplicitamente riportato nel costrutto. Per determinare il
tipo di e dobbiamo perciò far ricorso all’ambiente corrente ∆. In conclu-
sione l’ambiente corrente ∆ è utilizzato solo per controllare la validità di
una definizione nel senso che il tipo esplicitamente riportato coincide con
quello associato all’espressione di inizializzazione o al corpo della funzio-
ne. Quindi spezziamo ogni regola per le definizioni in due che definiscono
due predicati distinti. Il primo è della forma ⊢V d : ∆′ con F V (d) ⊆ V
che determina sintatticamente l’ambiente che dovremmo associare alla de-
finizione d se questa fosse valida. Il secondo predicato ∆ ⊢V d ci dice
invece se d è valida oppure no nel senso descritto sopra. Le regole per il
primo predicato che genera gli ambienti statici sono riportate in Tab. 8.1,
mentre le regole per il predicato che controlla la validità delle definizioni
sono in Tab. 8.2. Le prime due regole di ciascuna tabella sono per i valori
denotabili ρ(x) necessari nella premessa dell’ultima regola che gestisce il
8.3. SEMANTICA DI FUN 143

∆ ⊢V e : τ
∆ ⊢V k ∆ ⊢V nil
∆ ⊢V (x : τ = e)

∆ ⊢V d0 , ∆ ⊢V d0 : ∆0 , ∆[∆0 ] ⊢V ∪V0 d1
, ∆0 : V0
∆ ⊢V d 0 ; d 1

∆ ⊢V d 0 , ∆ ⊢V d 1
, BV (d0 ) ∩ BV (d1 ) = ∅
∆ ⊢V d0 and d1

∆ ⊢V d0 , ∆ ⊢V d0 : ∆0 , ∆[∆0 ] ⊢V ∪V0 d1
, ∆0 : V0
∆ ⊢V d0 in d1

⊢V d : ∆′ , ∆[∆′|V0 ] ⊢V ∪V0 d
, V0 = F V (d) ∩ BV (d)
∆ ⊢V rec d

form : ∆0 , ∆ ⊢V ae : T (form)
∆ ⊢V form = ae

∆ ⊢V ρ(x)
, ∀x ∈ W, ρ : W, ∆ : W
∆ ⊢V ρ
Tabella 8.2: Semantica statica per le definizioni di F UN:validità.
144 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

⊢V function f (form) : τ = e : [f = T (form) → τ ]

form : ∆0 , ∆[∆0 ] ⊢V ∪V0 e : τ


, ∆0 : V0
∆ ⊢V function f (form) : τ = e

∆ ⊢V ae : aet
, ∆(f ) = aet → τ
∆ ⊢V f (ae) : τ

T (•) = •
T (x : τ, form) = τ, T (form)

 •:∅

form : ∆0
 , ∆0 : V0 , x 6∈ V0
x : τ, form : ∆0 [x = τ ]



 ∆ ⊢V • : •


 ∆ ⊢V e : τ, ∆ ⊢V ae : aet


∆ ⊢V e, ae : τ, aet

⊢V (λ form.e : τ ) : T (form) → τ
f orm : ∆0 , ∆[∆0 ] ⊢V ∪V0 e : τ
, ∆0 : V0
∆ ⊢V (λ form.e : τ )

Tabella 8.3: Semantica statica per le funzioni di F UN.


8.3. SEMANTICA DI FUN 145

caso d = ρ presente nella sintassi. Infine dobbiamo aggiungere alle regole


della semantica statica per le espressioni la seguente

⊢V d : ∆0 , ∆ ⊢V d, ∆[∆0 ] ⊢V ∪V0 e
∆ ⊢V let d into e

Esaminiamo la definizione di funzione. Anche qui, come per tutte le


altre definizioni, abbiamo un predicato per la costruzione dell’ambiente
statico ed uno per determinare la validità della definizione. Le regole cor-
rispondenti sono le prime due in Tab. 8.3. La funzione T che associa una
lista di tipi ai parametri formali è definita per induzione strutturale dal pri-
mo gruppo di regole. Il predicato che consente di associare un ambiente
statico ad una lista form di parametri formali è invece definito dal secondo
gruppo di regole.
Per concludere lo studio della semantica statica di F UN commentiamo
le ultime due regole di Tab. 8.3. Esse servono per assegnare un tipo e de-
terminare la buona formatezza delle astrazioni che sono i valori denotabili
che associamo agli identificatori di funzione. Queste regole ci consentono
di applicare quelle sugli ambienti ρ delle Tab. 8.1 e 8.2 quando vi sono
funzioni.

8.3.2 Semantica dinamica


Vediamo adesso il comportamento dinamico di F UN. Come abbiamo già
detto più volte, riusciamo a definire la semantica dei linguaggi funzionali
senza utilizzare le memorie. Quindi avremo solo un ambiente dinamico
che lega variabili e valori denotabili che non contengono piú le locazio-
ni. Quindi dobbiamo riscrivere le regole della semantica dinamica delle
espressioni tenendo conto delle nuove strutture. Le regole sono in Tab. 8.4
e 8.5. Esse sono analoghe a quelle per I MP con l’eccezione delle regole
del blocco e dell’espressione condizionale che erano state rispettivamente
introdotte negli Esercizi 4.5 e 3.5. Le chiamata di funzione è trattata in-
sieme alle altre regole per la dichiarazione di funzione ed il passaggio dei
parametri.
146 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

ρ ⊢∆ e −
→e e0
ρ ⊢∆ x −
→e ρ(x)
ρ ⊢∆ e bop e′ −
→e e0 bop e′

ρ ⊢∆ e′ −
→e e1 →e e′
ρ ⊢∆ e −
ρ ⊢∆ k bop e′ −
→e k bop e1 →e uop e′
ρ ⊢∆ uop e −

ρ ⊢∆ k bop k ′ −
→e k ′′ , k ′′ = k bop k ′

→e t′ , t′ = uop t
ρ ⊢∆ uop t −
Tabella 8.4: Semantica dinamica per le espressioni di F UN.

Prima di riportare le regole per le definizioni, discutiamo la definizione


ricorsiva. Quello che vorremmo avere è una regola del tipo

ρ[ρ′ ] ⊢∆[∆′] d −
→∗d ρ′
, ρ′ : ∆′
ρ ⊢∆ rec d →∗d
− ′
ρ

Per evitare di dover indovinare l’ambiente dinamico ρ′ necessario a sod-


disfare le premesse della regola, come nel caso statico, utilizziamo due
regole. La prima ci dice di elaborare la definizione ricorsiva supponendo
di non conoscere niente delle variabili definite ricorsivamente. Questo fat-
to è espresso nella regola dalla notazione ρ \ V0 che formalmente definisce
ρ|Dom(ρ)\V0 (vedi Tab. 8.6). Il risultato di tale elaborazione è un ambiente
ρ0 che puó contenere variabili libere. A questo punto possiamo applicare la
seconda regola che ci permette di legare le variabili libere di ρ0 con rec ρ0
(vedi la semantica dinamica della definizione di funzione).
Le altre regole (Tab. 8.7) per le definizioni sono analoghe a quelle già
introdotte per le dichiarazioni di I MP.
Per concludere la descrizione della semantica dinamica di F UN ci ri-
mane da trattare la definizione di funzione, la chiamata e l’associazione tra
parametri formali ed attuali. Le regole sono tutte in Tab. 8.8.
8.3. SEMANTICA DI FUN 147

→d d′
ρ ⊢∆ d −
→e let d′ into e
ρ ⊢∆ let d into e −

→e e′
ρ[ρ0 ] ⊢∆[∆0] e −
, ρ0 : ∆0
→e let ρ0 into e′
ρ ⊢∆ let ρ0 into e −

ρ ⊢∆ let ρ0 into k −
→e k

ρ ⊢∆ e −
→e ê
ρ ⊢∆ if e then e′ else e′′ −
→e if ê then e′ else e′′

ρ ⊢∆ e −
→e tt
ρ ⊢∆ if e then e′ else e′′ −
→e e′

ρ ⊢∆ e −
→e ff
ρ ⊢∆ if e then e′ else e′′ −
→e e′′
Tabella 8.5: Semantica dinamica per le espressioni di F UN (cont.).


→d d  V0 = BV (d)′ ∩ F V (d)
ρ \ V0 ⊢∆[∆0 ] d − ′
, ⊢F V (d) d : ∆
→d rec d′  ∆0 = ∆′
ρ ⊢∆ rec d −
|V0

ρ ⊢∆ rec ρ0 −
→d {x = k | ρ0 (x) = k}∪
{f = (λform.let (rec ρ0 ) \ BV (form) into e) |
ρ0 (f ) = (λform.e)}
Tabella 8.6: Semantica dinamica per le definizioni ricorsive di F UN.
148 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

→e e′
ρ ⊢∆ e −
ρ ⊢∆ x : τ = k −
→d [x = k]
→d x : τ = e′
ρ ⊢∆ x : τ = e −

→d d′0
ρ ⊢∆ d 0 − →d d′1
ρ[ρ0 ] ⊢∆[∆0 ] d1 −
, ρ0 : ∆0
→d d′0 ; d1
ρ ⊢∆ d 0 ; d 1 − →d ρ0 ; d′1
ρ ⊢∆ ρ0 ; d1 −

→d d′0
ρ ⊢∆ d 0 −
ρ ⊢∆ ρ0 ; ρ1 −
→d ρ0 [ρ1 ]
→d d′0 in d1
ρ ⊢∆ d0 in d1 −

→d d′1
ρ[ρ0 ] ⊢∆[∆0 ] d1 −
, ρ0 : ∆0 ρ ⊢∆ ρ0 in ρ1 −
→d ρ1
→d ρ0 in d′1
ρ ⊢∆ ρ0 in d1 −

→d d′0
ρ ⊢∆ d 0 − →d d′1
ρ ⊢∆ d 1 −
→d d′0 and d1
ρ ⊢∆ d0 and d1 − →d ρ0 and d′1
ρ ⊢∆ ρ0 and d1 −

ρ ⊢∆ ρ0 and ρ1 −
→d ρ0 , ρ1

Tabella 8.7: Semantica dinamica per le definizioni di F UN.


8.3. SEMANTICA DI FUN 149

La prima regola è per l’elaborazione della definizione di funzione. Co-


me già per le procedure di I MP dobbiamo distinguere il caso dello scoping
statico e dello scoping dinamico . Il corpo dell’astrazione che leghiamo al
nome della funzione nell’ambiente dinamico è costituito dal blocco per le
espressioni in cui la parte dichiarativa è un ambiente. Per lo scoping statico
tale ambiente lega le variabili libere del corpo della funzione, mentre per lo
scoping dinamico è vuoto (le variabili libere saranno risolte nell’ambiente
attivo al momento della chiamata). Notiamo l’analogia con la dichiarazio-
ne di procedura in cui il corpo dell’astrazione è il blocco per i comandi in
cui la parte dichiarativa ha lo stesso ruolo discusso sopra. Oltre al costrutto
di blocco per espressioni o comandi, l’altra differenza con le procedure è
che qui registriamo nell’astrazione anche il tipo che avrà il valore retitui-
to dalla valutazione della funzione. Il tipo dei parametri è implicitamente
presente in form. A causa delle definizioni ricorsive, le astrazioni possono
contenere delle variabili libere (ricordiamo che la prima regola per la ricor-
sione non le lega nell’ambiente intermedio rec ρ0 ). Come conseguenza,
anche gli ambienti dinamici possono contenere variabili libere.
La seconda regola è invece per la chiamata di funzione. Anche in que-
sto caso facciamo ricorso al costrutto di blocco per le espressioni in cui la
definizione d è istanziata con f orm = ae per poter generare un ambiente in
cui i formali sono associati ai valori ottenuti dalla valutazione degli attuali.
Notiamo che il parametro attuale è istanziato nella definizione del blocco
al momento della chiamata e quindi viene valutato dopo la chiamata ma
prima della valutazione del corpo della funzione. Quindi il meccanismo di
passaggio dei parametri è ancora per valore.
Le regole per la valutazione degli attuali sono analoghe a quelle fornite
per I MP, cosı̀ come quelle per i formali. La differenza sostanziale è che non
si utilizzano memorie ed abbiamo un unico meccanismo di definizione dei
formali.
Cerchiamo di capire il funzionamento dinamico delle definizioni ricor-
sive con un esempio. Consideriamo la classica definizione ricorsiva del
fattoriale

rec f (x : int) : int = if x = 0 then 1else x × f (x − 1)


150 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

ρ ⊢∆ function f (form) : τ = e −→d [f = λform.let ρ′ into e : τ ],


 ′
ρ = ρ|F V (e)\BV (form) scoping statico
ρ′ = ∅ scoping dinamico

ρ ⊢∆ f (ae) −
→e let form = ae into e, ρ(f ) = λform.e : τ

→e e′
ρ ⊢∆ e − →ae ae′
ρ ⊢∆ ae −
→ae (e′ , ae)
ρ ⊢∆ (e, ae) − →ae (k, ae′ )
ρ ⊢∆ (k, ae) −

→ae ae′
ρ ⊢∆ ae −
→d form = ae′
ρ ⊢∆ form = ae −

ak ⊢ form : ρ0
ρ ⊢∆ form = ak −
→d ρ0

 •⊢•:∅

ak ⊢ form : ρ

k, ak ⊢ x : τ, form : ρ[x = k]

Tabella 8.8: Semantica dinamica per le funzioni di F UN.


8.4. ESERCIZI 151

Nel primo passo di elaborazione produciamo il legame

[f = λx : int.(if x = 0 then 1else x × f (x − 1)) : int]

che genera una chiusura per f che però contiene nel corpo la variabile
libera f (quella della chiamata ricorsiva). Il secondo passo di elebora-
zione ci consente di risolvere il riferimento libero alla chiamata del fat-
toriale sfruttando l’ambiente ρ0 appena ottenuto. Infatti l’ambiente finale
dell’elaborazione del fattoriale è

[f = λx : int.(let rec
[f = λx : int.(if x = 0 then 1else x × f (x − 1)) : int] into
(if x = 0 then 1else x × f (x − 1)) : int]

Quello che abbiamo fatto con questi passi di elaborazione è effettuare il


primo passo della definizione ricorsiva dicendo come deve essere legata la
variabile libera f nella chiusura. In effetti l’elaborazione di rec ρ0 produ-
ce un nuovo ambiente ρ1 in cui si va ad elaborare la definizione. I passi
che faremo nell’applicazione della funzione consistono nel valutare l’argo-
mento, nel generare un nuovo ambiente che sarà necessario per la chiamata
successiva ed infine nel valutare il corpo.

8.4 Esercizi
Esercizio 8.1. Definire per ogni categoria sintattica di F UN l’insieme delle
variabili legate.

S OLUZIONE . In modo analogo all’Esercizio 7.1 procediamo per induzione


strutturale sulla sintassi delle categorie sintattiche di F UN. Definiamo quin-
di una funzione BV : Dom → V ar, Dom ∈ {Exp, Def, F orm, AExp},
che deve essere il complemento rispetto alle variabili in Dom della corrsi-
pondente F V . Quindi otteniamo
152 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

espressioni

BV (k) = ∅
BV (x) = ∅
BV (e0 bop e1 ) = BV (e0 ) ∪ BV (e1 )
BV (uop e) = BV (e)
BV (let d into e) = BV (d) ∪ BV (e)
BV (if e then e0 else e1 ) = BV (e) ∪ BV (e0 ) ∪ BV (e1 )
BV (f (ae)) = ∅

definizioni

BV (nil) = ∅
BV (x : τ = e) = {x}
BV (d0 ; d1 ) = BV (d0 and d1 ) = BV (d0 ) ∪ BV (d1 )
BV (d0 in d1 ) = BV (d1 )
BV (rec d) = BV (d)
BV (form = ae) = BV (form)
BV (function f (form) : τ = e) = {f }
[
BV (ρ) = BV (ρ(x))
x∈Dom(ρ)

valori denotabili

BV (k) = ∅ BV (λ form.e : τ ) = BV (e) ∪ BV (form)

parametri formali e attuali

F ORMALI ATTUALI
BV (•) = ∅ BV (•) = ∅
BV (x : τ, form) = {x} ∪ BV (form) BV (ae) = ∅

2
8.4. ESERCIZI 153

Esercizio 8.2. Considerare il frammento di programma

let rec function g(x : int) : int =


if x = 0 then 0 else x + g(x − 1)
into g(2)

Se l’analisi statica non rileva errori, determinare il valore ottenuto dalla


valutazione del frammento nel caso di scoping statico e dinamico, mo-
strando le derivazioni delle principali transizioni.
S OLUZIONE . Il frammento di programma fornito è un blocco espressio-
ne e quindi la regola per l’analisi statica che dobbiamo applicare è quel-
la introdotta nell’Esercizio 4.5 modificata per tenere in considerazione le
definizioni ricorsive
⊢V d : ∆0 , ∆ ⊢V d, ∆[∆0 ] ⊢V ∪V0 e
, ∆0 : V0
∆ ⊢V (let d into e)

Quindi come primo passo dobbiamo determinare l’ambiente statico ∆0 ge-


nerato dalla parte definizione. La definizione è una definizione ricorsiva di
funzione e quindi prima determiniamo l’ambiente generato dalla funzio-
ne e controlliamo la sua validità usando le prime due regole di Tab. 8.3.
Da queste premesse generiamo l’ambiente per la definizione ricorsiva e
asseriamo la sua validità.
⊢V function g(x : int) : int = e : [g = int → int]
⊢V rec function g(x : int) : int = e : [g = int → int]

(x : int) : [x = int], ∆[x = int] ⊢V ∪{x} e


∆ ⊢V function g(x : int) : int = e
∆ ⊢V rec function g(x : int) : int = e
dove abbiamo posto per comodità

e = if x = 0 then 0 else x + g(x − 1)


154 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

ed abbiamo omesso la derivazione per ∆[x = int] ⊢V ∪{x} e in quanto del


tutto analoga a quelle simili viste per I MP. Adesso, per poter asserire la
correttezza del blocco espressione dobbiamo determinare se il corpo della
funzione è ben formato nell’ambiente corrente ∆ esteso con i legami pro-
dotti dall’elaborazione della definizione ricorsiva. Ma questo controllo è
lo stesso della premessa della validità della definizione di funzione, da cui
deriviamo la correttezza del frammento.
Adesso applichiamo quindi le regole della semantica dinamica per deter-
minare il valore restituito dalla valutazione del blocco (sono anch’esse ri-
portate nell’Esercizio 4.5 con l’omissione della memoria). Il primo passo
di valutazione consiste nel determinare l’ambiente ρ1 generato dall’elabo-
razione della definizione ricorsiva. Per questo applichiamo le regole di
Tab. 8.6.

ρ \ {g} ⊢∆ function g(x : int) : int = e − →d


[g = λ x : int.let ∅ into e : int]
ρ ⊢∆ rec function g(x : int) : int = e − →d
rec [g = λ x : int.let ∅ into e : int]
ρ ⊢∆ let rec function g(x : int) : int = e into g(2) − →d
let rec [g = λ x : int.let ∅ into e : int] into g(2)

Notiamo che l’ambiente nel corpo dell’astrazione è vuoto anche se la fun-


zione contiene la variabile libera g. Ma questa è stata tolta dall’ambiente
corrente ρ prima di elaborare la definizione in base alle condizioni della
prima regola di Tab. 8.6. Poniamo per comodità

ρ0 = [g = λ x : int.let ∅ into e : int]

Il secondo passo di valutazione permette di determinare l’ambiente ρ1 ge-


nerato dalla dichiarazione ricorsiva in base alla seconda regola di Tab. 8.6.

ρ ⊢∆ rec ρ0 −
→d ρ1

dove

ρ1 = [g = λ x : int.let (rec ρ0 ) \ {x} into e : int]


8.4. ESERCIZI 155

A questo punto terminiamo la valutazione raccogliendo contesto nella con-


clusione con le regole del blocco in modo standard giungendo nella confi-
gurazione

let ρ1 into g(2).

Il passo successivo consiste nel valutare la chiamata di funzione nell’am-


biente ρ[ρ1 ] (la premessa della regola del blocco che ci consente poi di
dedurre il valore della chiamata dall’ambiente ρ). Per la regola della chia-
mata generiamo un ambiente [x = 2] che va ad estendere quello corrente e
in quello risultante si valuta il corpo di g ottenendo

→∗e 2 + g(1)
ρ[ρ1 ][x = 2] ⊢V ′ if x = 0 then 0 else x + g(x − 1) −

La derivazione delle transizioni sopra è analoga a quelle viste per I MP. L’u-
nica differenza è che l’identificatore di funzione g definito ricorsivamen-
te deve essere nuovamente istanziato e la nuova chiamata valutata. Essa
sarà valutata nello stesso ambiente ρ[ρ1 ] questa volta estesa con il legame
[x = 1]. Proseguendo le derivazioni si ottiene il valore 3. 2

Esercizio 8.3. Modificare F UN per consentire il passaggio dei parame-


tri per nome; definire la semantica statica e dinamica dei costrutti che
vengono aggiunti.
S OLUZIONE . Ricordiamo che il passaggio dei parametri per nome consiste
nel valutare solo parzialmente gli argomenti al momento della chiamata di
funzione. Più precisamente si valutano gli attuali per legare le loro varia-
bili libere nell’ambiente della chiamata della funzione. Questa soluzione
prevede quindi la possibilità di valutare ulteriormente gli attuali durante la
valutazione del corpo della funzione.
La soluzione di questo esercizio è simile a quella dell’Esercizio 6.4 in cui
si definiscono i parametri passati per riferimento. Come prima cosa esten-
diamo la categoria sintattica dei formali per consentire di definire il nuovo
tipo di passaggio dei parametri. Quindi

form ::= . . . |name x : τ, form


156 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

Dobbiamo prevedere un nuovo tipo denotabile per poter associare un tipo


distinto ai parametri passati per nome da quelli passati per valore

dt ::= . . . |τ name

ed estendiamo conseguentemente la definizione della funzione T che as-


socia una lista di tipi ai parametri formali con la clausola

T (name x : τ, form) = τ name, T (form)

A questo punto dobbiamo vedere quali sono le variabile legate e libere nel
nuovo costrutto

BV (name x : τ, form) = {x} ∪ BV (form)


F V (name x : τ, form) = ∅

Possiamo, quindi, definire la semantica dei nuovi costrutti.


Semantica statica
Vediamo cosa succede per i formali. Dobbiamo aggiungere una regola alla
definizione del predicato che associa un ambiente statico a form per gestire
i parametri passati per nome. Il tipo che associamo al parametro è τ name
appositamente introdotto.

form : ∆0
, ∆0 : I0 ; x 6∈ I0
(name x : τ, form) : ∆0 [x = τ name]
Dobbiamo anche aggiungere un assioma alle espressioni che ci dice che il
valore associato a una variabile di tipo τ name è un valore di tipo τ

∆ ⊢I x : τ, ∆(x) = τ name

Questo assioma ci serve per garantire che nell’ambiente della chiamata il


valore di un parametro passato per nome è del tipo corrispondente.
Vediamo ora la regola da aggiungere per la valutazione degli attuali.

∆ ⊢I e : τ, ∆ ⊢I ae : aet
∆ ⊢I e, ae : τ name, aet
8.4. ESERCIZI 157

Notiamo che la precedente regola deve essere aggiunta a quelle già presenti
in Tab. 8.3 per consentire la valutazione di un attuale. Abbiamo quindi
del non determinismo nella valutazione degli attuali poiché abbiamo due
regole con le stesse premesse e diverse conclusioni. Tuttavia la sintassi
dei formali ed il controllo nella chiamata di funzione consente solo una
possibile soluzione e quindi globalmente il meccanismo di associazione
tra formali ed attuali rimane deterministico.
Dobbiamo infine aggiungere le regole della semantica statica per i nuovi
valori denotabili e : τ name
∆ ⊢V e : τ
⊢V (e : τ name) : τ name
∆ ⊢V (e : τ name)
a quelle di Tab. 8.1 e 8.2 rispettivamente.
Semantica dinamica
Come prima cosa dobbiamo estendere l’insieme dei valori denotabili con
gli elementi che possono avere tipo τ name. Aggiungiamo un insieme con
elementi e : τ name per ammettere espressioni con variabili libere (quelle
eventualmente presenti in e) in presenza di definizioni ricorsive. Notiamo
che adesso possiamo avere nella sintassi del nostro linguaggio definizioni
del tipo
rec name x : int = . . .
Sempre per la presenza di definizioni ricorsive dobbiamo anche modifi-
care le regole per la valutazione delle espressioni e la elaborazione delle
definizioni esplicitando l’insieme D delle variabili che sono correntemente
legate nell’ambiente dinamico. Avremo quindi regole del tipo
→e e′
ρ ⊢∆,D e − e →d d′
ρ ⊢∆,D d −
con ∆ : V e D ⊆ V e la compatibilità tra ambiente statico e dinamico vale
per ρ : ∆|D . Le variabili in V \ D sono quelle definite ricorsivamente.
Per distinguere il passaggio di parametri per nome da quello per valore
introduciamo l’insieme sintattico M ODI con metevariabile µ già utilizzato
nell’Esercizio 6.4 e definito come
µ ::= •|val, µ|name, µ
158 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE

⊢D,• T (•)

⊢D,µ T (ae)
⊢D,(val,µ) T (k, ae)

⊢D,µ T (ae)
, F V (e) ∩ D = ∅
⊢D,(name,µ) T (e, ae)
Tabella 8.9: Configurazioni terminali.

dove val indica il passaggio dei parametri per valore utilizzato in F UN e


name il nuovo passaggio per nome. Definiamo, ora la funzione M : aet →
M ODI come segue

M(•) = •
M(τ, aet) = val, M(aet)
M(τ name, aet) = name, M(aet)

la quale ci permette di associare ad una lista di tipi la lista dei modi in cui
i corrispondenti parametri devono essere passati. Definiamo le configura-
zioni per i parametri attuali

Γ∆,D,µ = {ae|∃aet.∆ ⊢I ae : aet, M(aet) = µ}

e le configurazioni terminali T∆,D,µ definite mediante il predicato di Tab. 8.9.

Definiamo, quindi, la semantica dinamica portandoci dietro, nelle regole,


il modo µ e l’insieme di variabili D nel caso in cui si stia valutando una
lista di parametri attuali ottenendo relazioni del tipo

ρ ⊢∆,D,µ ae →ae ae′


8.4. ESERCIZI 159

Adesso, se il passaggio è per valore le regole sono analoghe a quelle che


abbiamo definito per F UN
ρ ⊢∆,D e →e e′
ρ ⊢∆,D,(val,µ) (e, ae) →ae (e′ , ae)

ρ ⊢∆,D,µ ae →ae ae′


ρ ⊢∆,D,(val,µ) (k, ae) →ae (k, ae′ )
Se invece il passaggio è per nome, quando incontriamo un’espressione e
dobbiamo legare le sue variabili libere nell’ambiente corrente ρ

ρ ⊢∆,D,(name,µ) (e, ae) →ae (let ρ|F V (e) into e, ae)

La regola seguente ci dice come estendere la valutazione di una lista di


attuali quando incontriamo un parametro passato per nome.
ρ ⊢∆,D,µ ae →ae ae′
, F V (e) ∩ D = ∅
ρ ⊢∆,D,(name,µ) (e, ae) →ae (e, ae′ )

Vediamo quale ambiente viene associato ai parametri formali

• ⊢D,• • : ∅

ae ⊢D,µ f orm : ρ0
k, ae ⊢D,(val,µ) x : τ, f orm : ρ0 [x = k]

ae ⊢D,µ form : ρ0
(e, ae) ⊢D,(name,µ) (name x : τ, form) : ρ0 [x = e : τ name]
A questo punto possiamo definire le regole che associano parametri formali
e attuali
ρ ⊢∆,D,µ ae →ae ae′
, µ = M(T (form))
ρ ⊢∆,D form = ae →d form = ae′
160 CAPITOLO 8. UN SEMPLICE LINGUAGGIO FUNZIONALE


ae ⊢D,µ f orm : ρ0  ae ∈ T∆,D,µ
,
ρ ⊢∆,D f orm = ae −
→d ρ0  µ = M(T (f orm))
2
Capitolo 9

Conclusioni

In questo testo abbiamo descritto i concetti fondamentali della semanti-


ca operazionale strutturale e li abbiamo applicati ai linguaggi imperativi
e funzionali. La descrizione non ambigua dei costrutti principali dei lin-
guaggi di programmazione dovrebbe consentire ai progettisti di prevedere
l’effetto dei costrutti che stanno definendo, agli implementatori di produrre
compilatori fedeli alla definizione dei linguaggi ed agli utenti di utilizzare
in modo professionale le potenzialità offerte da un linguaggio.
La semantica operazionale tuttavia non è il solo modo di descrivere
formalmente un linguaggio di programmazione. Esistono altre tecniche
che richiameremo brevemente nella prossima sezione. Qui vogliamo solo
ricordare che la nostra scelta è stata guidata dall’esigenza pressante per
un testo didattico di utilizzare un formalismo matematicamente semplice
e vicino all’intuizione di chi abbia almeno qualche volta programmato.
Tuttavia la soluzione scelta consente di gestire tutti gli aspetti rilevanti dei
linguaggi [7, 20].
Dopo aver brevemente richiamato altre tecniche di definizione seman-
tica, nelle sezioni seguenti commentiamo le relazioni che intercorrono tra
la nostra trattazione ed i linguaggi di programmazione reali. Infine ripor-
tiamo alcuni suggerimenti per chi voglia approfondire gli aspetti trattati nel
testo ed alcuni ad essi correlati.

161
162 CAPITOLO 9. CONCLUSIONI

9.1 Altri approcci


Oltre la semantica operazionale che abbiamo trattato in questo testo, esi-
stono altri approcci per definire formalmente il significato dei costrutti dei
linguaggi. Tra le tecniche più diffuse ricordiamo la semantica denotazio-
nale e quella assiomatica. Vediamo quali sono gli aspetti peculiari delle
varie tecniche.

• Semantica operazionale. Il significato del costrutto di un linguaggio


è dato dalle operazioni che questo induce sulla macchina astratta
del linguaggio. Quindi la semantica operazionale descrive come è
ottenuto l’effetto dell’esecuzione di un costrutto.

• Semantica denotazionale. Il significato di un costrutto di un linguag-


gio è dato da un oggetto matematico. Quindi la semantica denota-
zionale descrive solamente l’effetto dell’esecuzione del cosrtutto.

• Semantica assiomatica. Le proprietà salienti dell’esecuzione di un


costrutto sono espresse mediante asserzioni logiche. Quindi, alcuni
aspetti dell’esecuzione e dei suoi effetti possono essere ignorati da
questa semantica.

Vogliamo ricordare che i differenti approcci per la definizione formale dei


linguaggi di programmazione non sono mutuamente esclusivi e non sono
in competizione l’uno con l’altro. Ad esempio la semantica operazionale
è più adatta delle altre tecniche a fornire suggerimenti a chi deve effettiva-
mente implementare un linguaggio e a gestire aspetti connessi con la con-
correnza ed i sistemi distribuiti [19]. La semantica denotazionale è invece
migliore per ragionare formalmente sui programmi e studiarne proprietà.
La semantica assiomatica fornisce un sistema logico che può essere uti-
lizzato per dimostrare proprietà in modo semi-automatico. Abbiamo però
visto nel testo che la semantica operazionale può essere definita anch’essa
in stile logico recuperando quindi tutti gli aspetti essenziali dell’approccio
assiomatico.
9.2. I LINGUAGGI REALI 163

9.2 I linguaggi reali


Nel presentare i linguaggi imperativi e funzionali abbiamo distinto gli
aspetti caratterizzanti degli uni (assegnamento e procedure) e degli altri
(espressioni e funzioni) trattandoli separatamente. Ancora una volta l’e-
sigenza didattica ci ha guidato in questa scelta. La realtà dei linguaggi di
programmazione è ben diversa.
Praticamente tutti i linguaggi imperativi dispongono di costrutti simili
alle funzioni. Di solito sono astrazioni il cui corpo è costituito da comandi
e che sono anche in grado di restituire un valore. Poiché le espressioni
fanno parte di tutti i linguaggi, nessuno vieta di poter scrivere un program-
ma di stile funzionale utilizzando un linguaggio tipo Pascal. Il paradigma
di programmazione imperativo è pensato tuttavia per esprimere modifi-
che della memoria e quindi i programmi che si scrivono non sono certo
funzionali.
I linguaggi funzionali dal canto loro sono più vicini al linguaggio F UN
che abbiamo utilizzato. Infatti l’esigenza di avere l’assegnamento e strut-
ture simili alla memoria è meno sentita e di solito è bene non utilizzarle
in questo contesto. In generale i linguaggi funzionali della famiglia ML
dispongono di costrutti tipo assegnamento ed iterativi, ma poiché il para-
digma impone uno stile matematico di definizione dei programmi, la loro
presenza nel linguaggio è molto più marginale di quanto non lo sia quella
delle funzioni nei linguaggi imperativi.
In conclusione, anche se abbiamo separato i concetti tipici dei due pa-
radigmi, il lettore dovrebbe aver acquisito a questo punto la familiarità con
la semantica operazionale strutturale per poter definire linguaggi ibridi e
studiarne il comportamento sia statico che dinamico.

9.3 Approfondimenti
In questa sezione riportiamo alcuni riferimenti ad altri testi sulla semanti-
ca dei linguaggi di programmazione che possono essere utilizzati per ap-
profondire alcuni degli aspetti trattati. Una introduzione molto dettagliata
agli aspetti semantici dei linguaggi di programmazione si trova in [22, 23],
benché questi libri non siano prevalentemente orientati alla semantica ope-
164 CAPITOLO 9. CONCLUSIONI

razionale. Per quanto riguarda la semantica operazionale ricordiamo, oltre


al lavoro di Plotkin [18] sul quale ci siamo prevalentemente basati nella
stesura del presente lavoro, anche il libro [16] in cui si descrive la semanti-
ca operazionale di un linguaggio imperativo e si discutono alcune tecniche
di verifica di proprietà dei programmi. Si può trovare in questo testo an-
che un’introduzione alla semantica assiomatica. Un altro testo che tratta la
semantica operazionale dei linguaggi imperativi è [11]. In entrambi i libri
citati non viene discusso l’aspetto statico legato ai linguaggi e nel secondo
si tratta solo lo scoping dinamico.
Gli aspetti legati alla definizione operazionale dei sistemi di tipi per i
linguaggi di programmazione sono affrontati in dettaglio in [8, 3].
La semantica denotazionale dei lingauggi di programmazione ed un
confronto con la semantica operazionale e assiomatica sono trattati in [25]
dove vi è anche una trattazione dell’induzione strutturale e sugli alberi di
derivazione. Quest’ultima può essere ulteriormente approfondita in [1] e
[5]. Altri testi in cui si affrontano tematiche di semantica denotazionale
sono [14, 21, 9, 24].
I testi base per approfondire gli aspetti legati ai fondamenti della pro-
grammazione funzionale ed al λ-calcolo sono [2, 10]. Per quanto riguarda
invece i veri linguaggi di programmazione funzionali, sono ottimi riferi-
menti per approfondimenti [17, 15].
Bibliografia

[1] P. Aczel. An introduction to inductive definitions. In Handbook of


mathematical logic. North-Holland, 1977.

[2] H.P. Barengregt. The Lambda Calculus. North-Holland, 1984.

[3] L. Cardelli. CRC Handbook of Computer Science and Engineering,


chapter 103, Type systems. CRC Press, 1997.

[4] L. Cardelli and P. Wegner. On understanding types, data abstraction,


and polymorphism. ACM Computing Surveys, december 1995.

[5] P Cousot and R. Cousot. Inductive definitions, semantics and abstract


interpretation. In Proceedings of POPL’92, pages 84–95, 1992.

[6] J.W. de Bakker and W.P. de Roever. A calculus for recursive


program schemes. In Proceedings of ICALP’72, pages 167–196.
North-Holland, 1972.

[7] P. Degano and C. Priami. Enhanced operational semantics. ACM


Computing Surveys, 28(2):352–354, 1996.

[8] H. Goguen. A typed operational semantics for type theory. PhD


thesis, University of Edinburgh, 1994.

[9] M.J.C. Gordon. The denotational description of programming


languages. Springer-Verlag, 1979.

[10] J.R. Hindley and J.P. Seldin. Introduction to combinators and λ-


calculus. Cambridge University Press, 1986.

165
166 BIBLIOGRAFIA

[11] C. Laneve. La descrizione operazionale dei linguaggi di


programmazione. Franco Angeli, 1998.

[12] P. Lucas. Formal definition of programming languages and systems.


In Proceedings of IFIP’71, 1971.

[13] J. McCarthy. Towards a mathematical science of computation. In


Information Processing 1962, pages 21–28, 1963.

[14] R.E. Milne and C. Strachey. A theory of programming language


semantics. Chapman and Hall, 1976.

[15] R. Milner, M. Tofte, R. Harper, and D. MacQueen. The definition of


standard ML (revised). MIT Press, 1997.

[16] F. Nielson and H.R. Nielson. Semantics with applications: a formal


introduction. Wiley, 1992.

[17] S.L. Peyton Jones. The implementation of functional programming


languages. Prentice-Hall, 1987.

[18] G. Plotkin. A structural approach to operational semantics. Technical


Report DAIMI FN-19, Aarhus University, Denmark, 1981.

[19] C. Priami. Enhanced Operational Semantics for Concurrency. PhD


thesis, Dipartimento di Informatica, Università di Pisa, March 1996.
Available as Tech. Rep. TD-08/96.

[20] C. Priami. Operational methods in theoretical computer science.


ACM Computing Surveys, 1999. To appear.

[21] J.E. Stoy. The Scott-Strachey approach to programming language


theory. MIT Press, 1977.

[22] R.D. Tennent. Principles of Programming Languages. Prentice Hall,


1981.

[23] R.D. Tennent. Semantics of Programming Languages. Prentice Hall,


1991.
BIBLIOGRAFIA 167

[24] D.A. Watt. Programming Language Semantics. Prentice Hall, 1990.

[25] G. Winskel. The formal semantics of programming languages. MIT


Press, 1993.

Potrebbero piacerti anche