Come collegare la teoria e l'implementazione per i cicli while?


8

Sto lavorando sul mio piccolo linguaggio di programmazione a fini educativi e ho riscontrato un piccolo problema. Ci sono alcune soluzioni diverse per questo, ma tutte sembrano ineleganti - e da quello che capisco, inutili. Ma leggendo i libri che ho e le ricerche su Google, non riesco a trovare la soluzione elegante.

Quindi, il problema è che sto costruendo il calcolo lambda di base quando lo capisco. Ho definito vero / falso come termini di astrazione. Sono in grado di combinare questi con funzioni per fare se / allora / altrimenti tipo di comportamento. Il problema si presenta con i loop. Posso definire un ciclo while di base tramite la ricorsione, ma in pratica, che provoca un overflow dello stack. A quanto ho capito, la solita soluzione sarebbe quella di eseguire l'ottimizzazione delle chiamate di coda, ma non vedo come posso - i condizionali sono definiti in linguaggio. Per questo motivo, il compilatore non sa che il corpo del ciclo while è in posizione di coda.

Il libro dei draghi si concentra sull'implementazione del ciclo presumendo che ci siano etichette e goto. Potrei certamente farlo. Sembra che altri linguaggi che non costruiscono in costrutti ciclici almeno costruiscano in condizionali e quindi facciano TCO. E potrei certamente farlo anche io. Ma la mia comprensione è che fino a quando posso applicare astrazioni ed eseguire riduzioni, allora i loop (e tutto il resto) dovrebbero poter essere costruiti da quei blocchi di base.

Quindi cosa mi sto perdendo? O è uno di quei casi in cui "puoi modellare qualsiasi cosa una volta che hai X e Y" non è lo stesso di "puoi modellare qualsiasi cosa una volta che hai X e Y su un computer reale" e sono necessari degli incorporamenti scopi?


Penso che tu abbia risposto alla tua domanda in quell'ultimo paragrafo. Solo perché la teoria dice che puoi fare qualcosa non significa che sia pratico farlo.
svick,

1
Molte lingue hanno condizionali e ricorsioni e implementano l'ottimizzazione della coda. Cerca oltre il libro dei draghi.
Dave Clarke,

Fammi capire bene: stai partendo dal puro λ-calcolo? Cioè, non ha nient'altro cheλe astrazioni?
Andrej Bauer,

svick - certo, ma come studente, non posso dire se questo è il caso qui o se ignoro qualcosa. dave clarke - molte lingue hanno incorporato i condizionali e implementano l'ottimizzazione del tail-call. Ho effettuato ricerche e non ho prodotto risultati per il condizionale in lingua e il TCO. Se hai un riferimento che ho trascurato ... Andrej Bauer - non del tutto, ma abbastanza vicino. Nessun tipo incorporato, nessuna funzione integrata. È possibile dichiarare funzioni e applicare funzioni. Approfondire la mia situazione particolare farebbe una domanda scadente.
Telastyn,

1
@Raphael L'utilizzo del calcolo lambda come lingua intermedia era una cosa importante negli anni '70 e '80. Credo che l'intento fosse rilevare ottimizzazioni semantiche. La mia comprensione (attenzione che non sono un esperto di tecniche di compilazione) è che le ottimizzazioni semantiche sono davvero difficili, mentre le ottimizzazioni locali possono pagare molto e sono più facili da vedere su una lingua con assegnazioni di registro e un uso moderato di goto. Tuttavia, le idee del calcolo lambda sono rilevanti per la progettazione del compilatore, ad esempio l'idea di assegnazione singola e il concetto di continuazione.
Gilles 'SO- smetti di essere malvagio'

Risposte:


5

Quindi sono riuscito a risolvere questo problema oggi. Il codice per il mio ciclo while:

while (condition: ~>bool) (body: ~>void) => void {
    if condition { 
        body; 
        while condition body; 
    };
}

Quando vado a compilare questo in CIL (un runtime basato su stack, importante per lo psuedocode, non importante per la risposta) sembra:

ldarg 0
<build closure from { body; while condition body; }>
call if

La cosa importante che mi mancava è che nel whilecodice, il condizionale era la cosa in posizione di coda. Dal punto di vista del compilatore, il blocco e la funzione while sono due funzioni separate, con due "code" separate. Ognuno di questi è facilmente valutato per la posizione della coda, rendendo possibile l'ottimizzazione nonostante la mancanza di condizionali incorporati.


5

Penso che ti manchi l'idea di continuazione . Sebbene il compilatore possa non fare affidamento su tale nozione, in qualità di progettista di compilatore con un linguaggio funzionale come linguaggio di origine o intermedio (o di destinazione), è importante comprendere tale concetto e tenerlo presente.

La continuazione di un pezzo di codice descrive a cosa esce il codice. In termini imperativi, incarna non solo la posizione in cui il codice salta o cade, ma anche lo stato del programma (stack e heap) in quel punto. In termini di calcolo lambda, la continuazione di un subterm è il contesto in cui viene valutato.

Quando traduci il codice imperativo in un linguaggio funzionale, una delle difficoltà è far fronte al codice che può uscire in diversi modi. Ad esempio, il codice può restituire o sollevare un'eccezione. Oppure, il corpo di un ciclo può continuare a controllare nuovamente la condizione o uscire del tutto dal ciclo ( breakcostrutto). Esistono due modi principali per far fronte a questo:

  • Multiplexing: rende il codice restituito un tipo di somma per tutte le possibili uscite. Nel caso di un corpo ad anello, sarebbe Continue | Break.
  • Stile di passaggio di continuazione : tradurre il codice in una funzione che accetta un parametro aggiuntivo che è la funzione da eseguire successivamente. Questo parametro aggiuntivo è la continuazione della funzione. Il codice che può uscire in modi diversi riceve uno di questi parametri per ciascuno dei modi.

Lo stile di passaggio di continuazione è il modo in cui le strutture di dati vengono incorporate nel calcolo lambda puro. Ad esempio, quando si rappresenta true comeλx,y.x e falso come λx,y.y, gli argomenti x e y sono le due possibili continuazioni e il booleano è un'istruzione "if" che seleziona l'una o l'altra continuazione.

In stile continuativo,

while (condition) body

viene tradotto in

let rec f (continuation) =
  if (condition, body (f (continuation)), continuation)

Nella traduzione di un programma in un tipico linguaggio imperativo in stile di passaggio di continuazione, la continuazione è sempre l'ultima cosa che esegue un pezzo di codice. Ad esempio, la continuazione di cui bodysopra viene eseguita dopo tutto il codice di body, quindi l'ottimizzazione delle chiamate di coda comporta la liberazione di tutte le variabili locali bodyappena prima di eseguire la continuazione.

Alcune lingue offrono continuazioni di prima classe con un costrutto come call-with-current-continuation . Call / cc non è generalmente suscettibile di ottimizzazione delle chiamate di coda - può in effetti essere un'operazione piuttosto costosa poiché può portare alla duplicazione dello stato dell'intero programma.

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.