Le regole per i processi continuano con i comandi di comunicazione mostrati in figu- ra 3.9.
Il comando send è la forma più generale di invio. Il costruttore Send della gram- matica astratta che lo modella ha tre argomenti:
1. exprls, la lista di espressioni da valutare e inviare sul canale; 2. ids, la lista di simboli degli identificativi dei destinatari; 3. P, il sotto-albero del processo rimanente.
La regola (P-send) dimostra la validità di una send. Per exprls è necessaria una dimostrazione, con le regole expr∗, che siano tutte espressioni semanticamente corrette. Il risultato della valutazione delle espressioni non è rilevante, e è stato omesso (ma deve esistere). Per verificare i destinatari viene interrogata la memoria: per ogni id deve esserci un binding per il valore NODEID.
Il costruttore Snd rappresenta sintatticamente il comando snd per inviare un mes- saggio etichettato. Il costruttore ha un campo per indicare l’etichetta L da usare. Il comando snd è di fatto zucchero sintattico, cioè esiste sempre un equivalente comando
send poiché le etichette altro non sono che valori costanti. Ciò è mostrato nella regola molto esplicitamente. Infatti è presente una premessa che verifica che L sia una costan- te dichiarata. La dimostrazione è demandata ad un processo equivalente attraverso il costruttore Send.
La regola (P-Recv) dimostra gli enunciati per i comandi receive . La semantica del comando consente di selezionare i messaggi attraverso un pattern-matching su un prefisso iniziale dei campi. Il costruttore è PReceive, con 3 argomenti:
1. exprlist, la lista di espressioni a cui testare il pattern-matching; 2. varlist, la lista di variabili per gli assegnamenti dei rimanenti campi; 3. P, il sotto-albero del processo.
I nomi in varlist che sono alla loro prima apparizione vengono legate nella memoria con un valore di tipo variabile necessario da instanziare per concludere la dimostra- zione. Proviamo ad analizzare come ogni premessa di (P-Recv) formalizza tutti gli aspetti descritti:
• varlistT Names(Γ) = ∅, assicura che nessuno dei nomi delle variabili nuove collida con un nome utilizzato nell’ambiente. Ad esempio, la premessa non per- mette di dimostrare il comando PReceive([ ], [number], PNil), dove la variabile number denota già il tipo numerico nell’ambiente.
• < Γ, σ, exprlist > →expr∗_, garantisce che le espressioni usate per testare
il pattern matching siano delle espressioni valutabili. Il risultato delle valuta- zioni non è rilevante perchè non è dato modo di verificare il pattern-matching staticamente (ma deve esistere).
• “varlist has all distinct names”, questa premessa specificata informalmente, intende evitare i casi di multipli assegnamenti ad una variabile. Ad esempio non permette di dimostrare comandi come PReceive([ ], [x; x], PNil).
• σ00= · · · , la memoria σ00estende il dominio di σ con tutti i nomi di varlist potenzialmente nuovi. Sull’intero dominio di σ replica il solito comportamento. Per ogni nome v nuovo in varlist è definito con un valore di tipo variabile tv ∈ Type. L’uso di un valore di tipo variabile (detto in gergo guessing) è un
artefatto che nasce perché il nome v è alla prima occorrenza nel programma, e non è possibile determinarne il valore di tipo. Al momento dell’instanzazione, per ottenere la dimostrazione, tutte le variabili devono essere instanziate op- portunatamente in maniera tale che la memoria σ00 permetta di dimostrare il rimanente sotto-albero P del processo.
• ∀z ∈ varlist.σ00
(z) ∈ Type, è una premessa che assicura che per gli assegnamen- ti sono state utilizzate soltanto variabili per valori di tipo. Ciò è assicurato per i nomi nuovi nella costruzione di σ00, ma non per tutti i nomi in varlist. Ad esempio evita di dimostrare conclusioni del tipo PReceive([ ], [. . . ; STemp], P), in cui STemp è una variabile di un sensore. Infatti la semantica di IoT-Lysa prevede che una variabile sensore possa essere usata solo in lettura.
• < Γ, σ00
, P > →procσ0, la dimostrazione del sotto-albero del processo. Il con-
testo della dimostrazione comprende la nuova memoria σ00che è stata creata, e la memoria complessiva della conclusione comprenderà anche le dichiarazioni aggiunte nel sotto-albero P.
Infine la regola (P-Rcv) è per il comando recv. Il comando è per ricevere messag- gi etichettati, ma senza alcun pattern-matching. Anche il comando recv è di fatto
Listing 3.3: Programma di esempio per il guessing. 1 node Foo =
2 p r o c e s s = r e c e i v e ( ; x ) ; n i l 3 p r o c e s s = t := x + 1 ; n i l
zucchero sintattico ed è sempre possibile creare un equivalente comando receive . La regola verifica che l’etichetta L sia una costante dichiarata nell’ambiente, e demanda la dimostrazione ad un comando receive equivalente senza pattern matching.
Segue la dimostrazione del programma nel listato 3.3 per mostrare in azione il meccanismo di guessing. Il sistema modellato è irreale ma presenta la situazione ideale per il guessing. Inizialmente, ipotizziamo che sia presente solamente il primo processo. Esso esegue una receive per un messaggio composto da un solo campo che viene assegnato alla variabile x e dopo di che termina. La variabile x è libera nel programma e non c’è alcun modo di inferire il suo tipo avendo a disposizione solo tale processo. La dimostrazione ristretta solamente al primo processo è:
σ0= [NODEID/Foo; Type(void)/x] σ0(x) ∈ Type
< Γ0, σ0, PNil > →procσ0
{x}T Names(Γ0) = ∅ < Γ0, σ, [ ] > →expr∗[ ] “[x] has all distinct values”
< Γ0, σ, PReceive([ ], [App(x, [ ])], PNil) > →procσ0
< Γ0, σ, PDecl(PReceive([ ], [App(x, [ ])], PNil)) > →bodyσ0
dove Γ0 è l’ambiente predefinito e σ = [NODEID / Foo] è la memoria vuota riservata
al nodo. In questo caso la variabile di tipo per x è stata instanziata con il valore Type(void) per ottenere con successo una dimostrazione. Poichè la variabile non ha ulteriori usi nel processo, qualunque altro valore di tipo sarebbe stato equivalente, ma la scelta del valore influisce sulla dimostrazione del secondo processo. Nel seguito abbreviamo le definizione dei due processi con:
p1= PDecl(PReceive([ ], [App(x, [ ])], PNil))
p2= PDecl(PAssign(t, App(+, [App(x, [ ]); App(1, [ ])), PNil))
La dimostrazione ristretta alla lista dei processi è dunque:
. . . < Γ, σ, p1> →bodyσ0 · · · ? ? ? · · · < Γ0, σ0, p2> →body∗X < Γ0, X, [ ] > →body∗X < Γ0, σ0, [p2] > →body∗X < Γ0, σ, [p1; p2] > →body∗X
dove il processo p1 è stato già dimostrato. Per concludere è necessario dimostrare p2
t 6∈ Names(Γ0)S{Foo; x}
Γ0(+) = Func(. . .) < Γ0, σ, [[]x; 1] > →expr∗[void; num]
< Γ0, σ0, App(+, [App(x, [ ]); App(1, [ ])) > 6→expr
< Γ0, σ0, PAssign(t, App(+, [App(x, [ ]); App(1, [ ])), PNil) > 6→proc
< Γ0, σ, PDecl(PAssign(t, App(+, [App(x, [ ]); App(1, [ ])), PNil)) > 6→body
sfortunatamente la dimostrazione fallisce perché non è possibile ottenere un matching con la signature dell’operatore. Diventa evidente che per ottenere la dimostrazione del nodo l’unica soluzione è che la variabile x venga legata al valore di tipo Type(number), in modo che abbia successo la dimostrazione di entrambi i processi.
Capitolo 4
Implementazione.
In questo capitolo viene descritto come è stato realizzato il type system presentato nel capitolo precedente. Il type-checker si inserisce all’interno di un compilatore per il linguaggio IoT-Lysa reperibile a [13], scritto in F# [25, 26, 27, 28]. La libreria è identificata dal namespace TypeChecker. All’interno sono presenti i moduli Domain, Utils, Exceptions rispettivamente per contenere le definizioni del dominio, funzionalità ausiliari e le eccezioni. I moduli sono dichiarati con l’attributo AutoOpenAttribute, un attributo speciale per indicare che il modulo è automanticamente aperto se il namespa- ce lo è altrettanto, ovvero gli oggetti del modulo sono accessibili senza dover indicare l’intero percorso logico. Il codice dell’algoritmo è contenuto nel sotto-namespace Al- gorithms. Quest’ultimo è suddiviso in moduli ognuno dedicato alla validazione di un costrutto sintattico.
Di seguito sono descritti gli aspetti chiave dell’implementazione e sono mostrati anche estratti del codice sorgente delle principali funzionalità (dai quali sono stati rimossi i commenti). Sono usate metologie tipiche dei linguaggi funzionali e di F#.