Come vengono gestiti gli effetti collaterali in semantica?


19

Nella sezione "Introduzione ai linguaggi di programmazione" di Anthony Aaby sulla semantica , fa la seguente osservazione:

Gran parte del lavoro nella semantica dei linguaggi di programmazione è motivato dai problemi incontrati nel tentativo di costruire e comprendere programmi imperativi --- programmi con comandi di assegnazione. Poiché il comando di assegnazione riassegna i valori alle variabili, l'assegnazione può avere effetti imprevisti in parti distanti del programma.

Questo mi sembra un'ammissione notevole, che consentire effetti collaterali motiverebbe gran parte del lavoro in semantica.

In che modo l'esistenza di effetti collaterali in un linguaggio di programmazione influisce sulla capacità di associare un programma a un modello computazionale? Esistono approcci alla gestione dello stato che possono migliorare questo processo pur consentendo effetti collaterali?


Questo dovrebbe essere etichettato come una domanda debole? Poiché "gran parte del lavoro in semantica [...] è motivato da [effetti collaterali]", sicuramente non puoi aspettarti una risposta breve e rigorosa.
Radu GRIGore,

1
@Radu: Su MO, questo verrebbe probabilmente taggato [big-picture], che per lo più non ci sono [soft-question] o CW lì.
Charles Stewart,

Il tag big-picture è ancora meglio. Me ne sono dimenticato.
Radu GRIGore,

Buon consiglio; Ho aggiunto il tag.
Shane,

Risposte:


18

Basandosi sulla risposta di Charles, la principale difficoltà nella teoria dei linguaggi di programmazione è che la nozione naturale di equivalenza dei programmi non è in genere una rigida uguaglianza né nella semantica matematica più semplice che si possa dare, o nel modello macchina sottostante. Ad esempio, considera il seguente bit di codice simile a Java:

Object x = new Object();
Object y = new Object();
... some more code ...

Quindi questo programma crea un oggetto e lo denomina x, quindi crea un secondo oggetto chiamato y, quindi continua l'esecuzione di altro codice. Supponiamo ora che un programmatore decida di invertire l'ordine di allocazione di questi due oggetti:

Object y = new Object();
Object x = new Object();
... some more code ...

Ora, fai la domanda: questo refactoring cambia il comportamento del programma? Da un lato, sulla macchina sottostante, xey verranno allocati in posizioni diverse nelle due esecuzioni del programma. Quindi, in questo senso, il programma si comporta diversamente.

Ma in un linguaggio simile a Java, puoi solo testare i riferimenti per l'uguaglianza e non per l'ordine, quindi questa è una differenza che "un po 'di codice in più" non può osservare . Di conseguenza, la maggior parte dei programmatori si aspetta che l'inversione dell'ordine non faccia alcuna differenza per la risposta finale e la maggior parte degli autori di compilatori si aspettano di essere in grado di eseguire riordini e ottimizzazioni su questa base. (D'altra parte, in un C-come il linguaggio, è possibile confrontare i puntatori per l'ordinazione, gettandoli in interi prima, e così questo riordino non non necessariamente conservare comportamento osservabile.)

Una delle domande centrali della semantica è rispondere alla domanda su quando due programmi sono notevolmente equivalenti. Poiché la nostra nozione di osservazione dipende dalle caratteristiche del linguaggio di programmazione, finiamo con una definizione come "due programmi sono equivalenti quando nessun programma client può calcolare risposte diverse in base alla ricezione di tali programmi come input". La quantificazione su tutti i programmi client è ciò che rende difficile questa domanda - sembra che tu debba finire per dire qualcosa su tutti i possibili programmi client per dire qualcosa su due particolari pezzi di codice.

Il trucco con la semantica denotazionale è quello di dare un'interpretazione matematica che ti consenta di evitare questa quantificazione universale - dici che il significato di un pezzo di codice è un valore matematico e li confronti confrontando per vedere se sono matematicamente uguali o non. Questo è locale (cioè composizionale) e non comporta la quantificazione su tutti i possibili clienti. (Devi mostrare che la semantica denotazionale implica l'equivalenza contestuale perché sia ​​sana, ovviamente. Quando è completa - quando l'uguaglianza denotazionale è esattamente la stessa dell'equivalenza contestuale, diciamo che la semantica è "completamente astratta".)

Ma significa che è necessario assicurarsi che la semantica denotazionale convalidi tali equivalenze. Quindi, per questo esempio, se si desidera fornire una semantica denotazionale per questo linguaggio simile a Java, è necessario assicurarsi non solo che chiamare new prenda un heap e restituisca un nuovo heap con l'oggetto appena creato, ma che il significato del programma è invariante uguale in tutte le permutazioni dell'heap di input. Ciò può comportare strutture matematiche piuttosto complesse (ad esempio, in questo caso lavorare in una categoria che garantisce che tutto funzioni in un gruppo di permutazione adatto).


"due programmi sono equivalenti quando nessun programma client può calcolare risposte diverse in base alla ricezione di tali programmi come input." Sono confuso da questo. Se hai un programma X e un programma client Y, allora prendo il significato che Y 'chiama in' X. Ma poi sembra che tu abbia letto il testo di X come input , nel qual caso difficilmente chiamerei Y un 'cliente' di X. Potresti chiarire, per favore?
Radu GRIGore,

1
Per "client di X" intendo esattamente lo stesso "contesto del programma", che è solo un "programma più grande che contiene X come sotterfugio".
Neel Krishnaswami,

Quindi usi 'X è un client di Y' in modo intercambiabile con 'X legge Y come input' perché pensi a X come a un lambda applicato a Y? Ha senso, ma è un po 'contorto quando parli di Java. :)
Radu GRIGore,

1
@RaduGRIGore: il contesto del programma significa qualcos'altro. Stai leggendo correttamente il post, ma se X legge il codice sorgente di Y come input (che è il modo in cui interpreto il post), puoi distinguere ogni due programmi sintatticamente diversi; invece se Y è una funzione lambda su X, puoi distinguere troppo pochi programmi. Il commento di Neel sul "contesto del programma" è la definizione corretta: un contesto di programma Y è un programma con un buco nel suo AST, dove è possibile posizionare (significativamente) due diversi frammenti di programma X1 e X2.
Blaisorblade,

@NeelKrishnaswami: potresti forse chiarire cosa intendi nel post? Puoi semplicemente continuare a usare il tuo esempio e parlare di un programma in cui puoi inserire l'uno o l'altro frammento.
Blaisorblade,

12

Esistono ovviamente modi per gestire gli effetti nella semantica (denotazionale). Ad esempio, possiamo usare l' idea di Eugenio Moggi secondo cui gli effetti computazionali sono monadi (questa idea è stata usata anche nel design di Haskell). Uno dei problemi è che le monadi sono difficili da combinare. Gordon Plotkin e John Power hanno suggerito un perfezionamento delle monadi di Moggi alle teorie di Lawvere o teorie algebriche come vengono anche chiamate, che comprende effetti algebrici (gli effetti più comuni sono algebrici, come stato, I / O, non determinismo, ma le continuazioni sono non). Per un trattamento completo, vedi la tesi di Matija Pretnar .

Dovrei anche menzionare la possibile semantica dei mondi per lo stato locale, sviluppata da Frank Oles e John Reynolds (scusate, non riesco a trovare un collegamento migliore, questa roba è del 1982), che precede le monadi di Moggi. Hanno usato categorie di presheave per fornire una semantica di un linguaggio algolico che modellava correttamente molti aspetti dello stato locale (ma non tutti, penso che il modello consentisse lo snapback, ma forse la mia memoria mi ha sbagliato).


1
Sì, la semantica della categoria di funzioni non ha convalidato tutte le equivalenze di Meyer-Sieber. Peter O'Hearn e Robert Tennant hanno sviluppato una versione parametrica della semantica della categoria di funzione a metà degli anni '90 che (IIRC) ha ottenuto tutti gli esempi di Meyer-Sieber, ma non so se fosse completamente astratta o meno.
Neel Krishnaswami,

Il modello di O'Hearn e Tennent non è completamente astratto. Questo è discusso nel documento stesso. Ma la raffinatezza di O'Hearn e Reynolds usando il calcolo lambda lineare è completamente astratta fino al secondo ordine. Si rompe per il terzo ordine, esempi sono le equivalenze studiate da Ahmed, Dreyer, Birkedal et al.
Uday Reddy,

12

Matthias Felleisen ha presentato una soluzione convincente al problema degli effetti collaterali in semantica nella sua serie su "Teorie sintattiche di controllo e stato".

Questa linea di lavoro ha portato alla macchina CESK, un semplice framework di macchine astratte in grado di modellare in modo conciso linguaggi funzionali, orientati agli oggetti, imperativi e persino logici. Il framework CESK gestisce non solo gli effetti collaterali, ma anche costrutti di controllo "complessi" come eccezioni, continuazioni, pigrizia e persino discussioni.

La macchina CESK e la semantica operativa a piccoli passi più in generale, sono stati di fatto lo standard nella teoria del linguaggio di programmazione per circa due decenni.

In breve, una macchina CESK è una macchina a piccoli passi con quattro componenti per descrivere ogni stato della macchina: la stringa di controllo (una generalizzazione del contatore del programma), l'ambiente, un negozio (chiamato anche heap) e l'attuale continuazione.

L'ambiente mappa le variabili agli indirizzi; il negozio associa gli indirizzi ai valori.

Questo rende semplice la modellazione di variabili mutabili: basta cambiare il valore al suo indirizzo.

Inoltre semplifica la modellazione di puntatori e allocazione dinamica: basta fare in modo che gli indirizzi dei negozi siano valori di prima classe.

In modo simile, le continuazioni di prima classe derivano dal renderli valori indirizzabili.


6

In che modo l'esistenza di effetti collaterali in un linguaggio di programmazione influisce sulla capacità di associare un programma a un modello computazionale?

Non necessariamente lo rende difficile, ma impone restrizioni sul modo in cui la semantica delle espressioni più grandi può essere costruita da quelle più piccole. Può interagire molto male con alcuni altri costrutti di programmazione, ad esempio, se si vuole dare una semantica denotazionale in stile scozzese per un linguaggio che consenta di assegnare funzioni di ordine superiore a riferimenti globali.

Non sono semplicemente gli effetti collaterali come lo stato che causano il problema. Linguaggi imperativi semplici come il linguaggio di comando custodito di Dijkstra hanno questo tipo di effetti collaterali e hanno una buona semantica. I problemi sorgono con estensioni del lambda-calcolo con il tipo di semantica operativa che ci si aspetta dai linguaggi di programmazione anche in assenza di effetti collaterali: il primo, PCF di Plotkin, è stato dato modelli denotazionali relativamente presto, ma la semantica non era completamente astratta, il che significa che la semantica denotazionale era eccessivamente generale, non esattamente corrispondente alla loro semantica operativa. Alla fine degli anni '80 PCF ricevette finalmente una semantica denotazionale completamente astratta con la semantica del gioco, che non assomiglia affatto alla semantica teorica dell'ordine di Scott. La concorrenza non ha ancora ricevuto un trattamento denotazionale completamente adeguato.

Molti mettono in dubbio l'importanza di questo tipo di semantica. Possiamo sempre fornire una sorta di semantica operativa, anche se quella "semantica" è solo la fonte del programma e i nomi di alcune macchine che hanno compilato ed eseguito il programma: per questo motivo Strachey ha condannato la semantica operativa. Ma la semantica dell'operazione strutturale di Plotkin ha mostrato come la semantica operativa può essere separata dai modelli di macchine, e il lavoro di Pitt ha mostrato come tale semantica può supportare ragionamenti simili su programmi e linguaggi di programmazione con la semantica denotazionale. Pertanto la semantica operativa è una valida alternativa alla semantica denotazionale e sono state applicate con successo a un numero considerevole di linguaggi di programmazione come Standard ML.

Esistono approcci alla gestione dello stato che possono migliorare questo processo pur consentendo effetti collaterali?

In una certa misura, le difficoltà a fornire la semantica corrispondono alla difficoltà di fornire potenti linguaggi di programmazione che si comportano come ci si aspetterebbe. Le decisioni di progettazione pragmaticamente motivate, come evitare l'uso dello stato globale insieme alla concorrenza, in genere attraverso la concorrenza di passaggio di messaggi, semplificano la fornitura di semantica.


Il PCF di Scott non ha stato e nemmeno quello di Scott, vero? Vedi en.wikipedia.org/wiki/…
Andrej Bauer

@Andrej: Err, piuttosto, dato che Luke Ong ha supervisionato il mio D.Phil, non dovrei commettere quell'errore. Ho pubblicato un riepilogo del teaser del PCF di Milner e del LCF di Scott che è più ... succinto di quello di WP come storia dell'LtU : lambda-the-ultimate.org/node/2196 Mi viene in mente che potresti avere accesso allo Scott scomparso (1969) manoscritto ...
Charles Stewart,

Sarebbe il PCF di Plotkin, penso :-) Posso provare a procurarmi il manoscritto, ma in realtà non ce l'ho.
Andrej Bauer,

Ma il punto rimane che PCF non ha stato. Quale "ragione" stai dicendo a Strachey di condannare la semantica operativa? Non mi era chiaro. L'ultimo paragrafo contraddice quello che hai detto prima, vale a dire, i comandi custoditi hanno una buona semantica, ma PCF no!
Uday Reddy,

@Andrej, Uday: ho risolto il mio incarico, meno di tre anni dopo.
Charles Stewart,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.