La distinzione tra codice interpretato e compilato è probabilmente una finzione, come sottolineato dal commento di Raffaello :
the claim seems to be trivially wrong without further assumptions: if there is
an interpreter, I can always bundle interpreter and code in one executable ...
Il fatto è che il codice viene sempre interpretato, dal software, dall'hardware o da una combinazione di entrambi, e il processo di compilazione non può dire quale sarà.
Ciò che percepisci come compilazione è un processo di traduzione da una lingua (per l'origine) a un'altra lingua T (per l'obiettivo). E, l'interprete per la S è in genere diverso dal interprete per T .STST
Il programma compilato viene tradotto da una forma sintattica in un'altra forma sintattica P T , in modo tale che, data la semantica prevista delle lingue S e T , P S e P T abbiano lo stesso comportamento computazionale, fino ad alcune cose che di solito stanno cercando di cambiare, possibilmente per ottimizzare, come complessità o semplice efficienza (tempo, spazio, superficie, consumo di energia). Sto cercando di non parlare di equivalenza funzionale, poiché richiederebbe definizioni precise.PSPTSTPSPT
Alcuni compilatori sono stati effettivamente utilizzati semplicemente per ridurre le dimensioni del codice, non per "migliorare" l'esecuzione. Questo è stato il caso del linguaggio usato nel sistema Plato (sebbene non lo chiamassero compilare).
Si può prendere in considerazione il codice completamente compilato se, dopo il processo di compilazione, non è più necessario l'interprete per . Almeno, questo è l'unico modo in cui posso leggere la tua domanda, come una questione ingegneristica piuttosto che teorica (dal momento che, teoricamente, posso sempre ricostruire l'interprete).S
Una cosa che può sollevare problemi, a parte, è la meta-circolarità . Questo è quando un programma manipolerà le strutture sintattiche nel suo linguaggio , creando frammenti di programma che vengono poi interpretati come se fossero stati parte del programma originale. Dal momento che puoi produrre frammenti di programma arbitrari nel linguaggio S come risultato di calcoli arbitrari manipolando frammenti sintattici insignificanti, immagino che tu possa rendere quasi impossibile (da un punto di vista ingegneristico) compilare il programma nel linguaggio T , in modo che la guida generare frammenti di T . Quindi sarà necessario l'interprete per S , o almeno il compilatore da S aSSTTSS perla compilazione al volodi frammenti generati in S (vedi anchequesto documento).TS
Ma non sono sicuro di come questo possa essere formalizzato correttamente (e non ho tempo per ora). E impossibile è una parola grossa per un problema che non è formalizzato.
Altre osservazioni
Aggiunto dopo 36 ore. Potresti voler saltare questo sequel molto lungo.
I molti commenti a questa domanda mostrano due punti di vista del problema: una visione teorica che lo considera insignificante e una visione ingegneristica che purtroppo non è così facilmente formalizzata.
Esistono molti modi per esaminare l'interpretazione e la compilazione e proverò a disegnarne alcuni. Cercherò di essere il più informale possibile
Il diagramma della pietra tombale
Una delle prime formalizzazione (dall'inizio degli anni '60 alla fine del 1990) sono i diagrammi T o
Tombstone . Questi diagrammi presentavano in elementi grafici componibili il linguaggio di implementazione dell'interprete o del compilatore, il linguaggio di origine interpretato o compilato e il linguaggio di destinazione nel caso dei compilatori. Versioni più elaborate possono aggiungere attributi. Queste rappresentazioni grafiche possono essere viste come assiomi, regole di inferenza, utilizzabili per derivare meccanicamente la generazione di processori da una prova della loro esistenza dagli assiomi, alla Curry-Howard (anche se non sono sicuro che sia stato fatto negli anni sessanta :).
Valutazione parziale
Un'altra visione interessante è il paradigma della valutazione parziale . Sto prendendo una semplice visione dei programmi come una sorta di implementazione di funzioni che calcola una risposta dati alcuni dati di input. Poi un interprete
per il linguaggio S è un programma che prende un programma p S
scritto in S e dati d per quel programma, e calcola il risultato in base alla semantica di S . Valutazione parziale è una tecnica per un programma specializzato di due argomenti un 1 e un 2 , quando un solo argomento, dire un 1ioSSpSSdSun'1un'2un'1, è conosciuto. L'intento è quello di avere una valutazione più veloce quando finalmente ottieni il secondo argomento . È particolarmente utile se un 2 cambia più spesso di un 1 poiché il costo della valutazione parziale con un 1 può essere ammortizzato su tutti i calcoli in cui cambia solo un 2 .un'2un'2un'1un'1un'2
Questa è una situazione frequente nella progettazione dell'algoritmo (spesso l'argomento del primo commento su SE-CS), quando una parte più statica dei dati è pre-elaborata, in modo che il costo della pre-elaborazione possa essere ammortizzato su tutte le applicazioni dell'algoritmo con più parti variabili dei dati di input.
Questa è anche la situazione stessa degli interpreti, poiché il primo argomento è il programma da eseguire, e di solito viene eseguito più volte con dati diversi (o ha sottoparti eseguite più volte con dati diversi). Quindi diventa un'idea naturale specializzare un interprete per una valutazione più rapida di un determinato programma valutandolo parzialmente su questo programma come primo argomento. Questo può essere visto come un modo per compilare il programma, e ci sono stati lavori di ricerca significativi sulla compilazione mediante valutazione parziale di un interprete sul suo primo argomento (programma).
Il teorema di Smn
Il punto interessante dell'approccio di valutazione parziale è che affonda le sue radici nella teoria (sebbene la teoria possa essere un bugiardo), in particolare nel
teorema di Smn di Kleene . Sto provando qui a darne una presentazione intuitiva, sperando che non sconvolga i puri teorici.
Data una numerazione di Gödel delle funzioni ricorsive, è possibile visualizzare φ come hardware, in modo che dato il numero di Gödel p
(leggi il codice oggetto ) di un programma φ p sia la funzione definita da p (cioè calcolata dal codice oggetto sul tuo hardware ).φφpφpp
Nella sua forma più semplice, il teorema è dichiarato in Wikipedia come segue (fino a un piccolo cambiamento nella notazione):
Data una numerazione di Gödel delle funzioni ricorsive, esiste una funzione ricorsiva primitiva σ di due argomenti con la seguente proprietà: per ogni numero di Gödel q di una funzione calcolabile parziale f con due argomenti, le espressioni φ σ ( q , x ) ( y ) e f ( x , y ) sono definiti per le stesse combinazioni di numeri naturali x ed y , ei loro valori sono uguali per qualsiasi combinazione. In altre parole, la seguente uguaglianza estensionale di funzioni vale per ogniφσqfφσ(q, x )( y)f( x , y)Xy :
Xφσ( q, x )≃ λ y. φq( x , y) .
Ora, prendendo come interprete I S , x come codice sorgente di un programma p S e y come dati d per quel programma, possiamo scrivere:
qioSXpSydφσ( IoS, pS)≃ λ d. φioS( pS, d) .
possono essere visti come l'esecuzione dell'interprete I S
sull'hardware, cioè, come un black-box pronto a interpretare i programmi scritti in linguaggio S .φioSioSS
La funzione può essere vista come una funzione specializzata nell'interprete I S per il programma P S , come nella valutazione parziale. Pertanto il numero di Gödel σ ( I S , p S ) può essere visto trovi codice oggetto che è la versione compilata del programma p S .σioSPSσ( IoS, pS)pS
Quindi la funzione può essere visto come una funzione che prende come argomento il codice sorgente di un programma q S
scritto nella lingua S , e restituisce la versione del codice oggetto per quel programma. Quindi C S è ciò che viene solitamente chiamato un compilatore.CS= λ qS. σ( ( IS, qS)qSSCS
Alcune conclusioni
Tuttavia, come ho detto: "la teoria può essere un bugiardo", o in realtà sembra esserlo. Il problema è che non sappiamo nulla della funzione . In realtà ci sono molte di queste funzioni, e la mia ipotesi è che la dimostrazione del teorema possa usare una definizione molto semplice per essa, che potrebbe non essere migliore, dal punto di vista ingegneristico, della soluzione proposta da Raffaello: semplicemente raggruppare il codice sorgente q S con l'interprete che S . Questo può sempre essere fatto, in modo che possiamo dire: la compilazione è sempre possibile.σqSioS
Formalizzare una nozione più restrittiva di cosa sia un compilatore richiederebbe un approccio teorico più sottile. Non so cosa sia stato fatto in quella direzione. Il vero lavoro svolto sulla valutazione parziale è più realistico dal punto di vista ingegneristico. E naturalmente ci sono altre tecniche per scrivere compilatori, tra cui l'estrazione di programmi dalla prova delle loro specifiche, sviluppata nel contesto della teoria dei tipi, basata sull'isomorfismo di Curry-Howard (ma sto uscendo dal mio dominio di competenza) .
Il mio scopo qui è stato quello di mostrare che l'osservazione di Raffaello non è "pazza", ma un sano promemoria che le cose non sono ovvie e nemmeno semplici. Dire che qualcosa è impossibile è un'affermazione forte che richiede definizioni precise e una prova, se non altro per avere una comprensione precisa di come e perché è impossibile . Ma costruire una corretta formalizzazione per esprimere tale prova può essere piuttosto difficile.
Ciò detto, anche se una specifica funzionalità non è compilabile, nel senso inteso dagli ingegneri, le tecniche di compilazione standard possono sempre essere applicate a parti dei programmi che non utilizzano tale funzionalità, come osservato dalla risposta di Gilles.
Seguendo le osservazioni chiave di Gilles che, a seconda della lingua, alcune cose possono essere fatte in fase di compilazione, mentre altre devono essere fatte in fase di esecuzione, richiedendo quindi un codice specifico, possiamo vedere che il concetto di compilazione è in realtà mal definito, e probabilmente non è definibile in alcun modo soddisfacente. La compilazione è solo un processo di ottimizzazione, come ho cercato di mostrare nella sezione di valutazione parziale , quando l'ho confrontato con la preelaborazione dei dati statici in alcuni algoritmi.
Come processo di ottimizzazione complesso, il concetto di compilazione appartiene in realtà a un continuum. A seconda delle caratteristiche della lingua o del programma, alcune informazioni potrebbero essere disponibili staticamente e consentire una migliore ottimizzazione. Altre cose devono essere rimandate al runtime. Quando le cose si mettono davvero male, tutto deve essere fatto in fase di esecuzione almeno per alcune parti del programma e raggruppare il codice sorgente con l'interprete è tutto ciò che puoi fare. Quindi questo raggruppamento è solo la parte bassa di questo continuum di compilazione. Gran parte della ricerca sui compilatori riguarda la ricerca di modi per fare staticamente ciò che si faceva in modo dinamico. La garbage collection in fase di compilazione sembra un buon esempio.
Si noti che dire che il processo di compilazione dovrebbe produrre codice macchina non è di aiuto. Questo è esattamente ciò che il bundling può fare in quanto l'interprete è il codice macchina (beh, la cosa può diventare un po 'più complessa con la compilazione incrociata).