Interpretato vs compilato: una distinzione utile?


29

Qui vengono poste molte domande sugli strumenti linguistici interpretati e compilati. Mi chiedo se la distinzione abbia davvero senso. (In realtà le domande riguardano in genere le lingue, ma stanno davvero pensando alle implementazioni più popolari di tali lingue).

Oggi quasi nessuna implementazione è interpretata rigorosamente. cioè praticamente nessuno analizza ed esegue il codice una riga alla volta. Inoltre, anche l'implementazione che si compila in codice macchina sta diventando meno comune. Sempre più spesso, i compilatori prendono di mira una sorta di macchina virtuale.

In effetti, la maggior parte dell'implementazione sta convergendo sulla stessa strategia di base. Il compilatore produce bytecode che viene interpretato o compilato in codice nativo tramite un JIT. È davvero un mix delle idee tradizionali di compilazione e interpretazione.

Quindi chiedo: esiste una distinzione utile tra implementazioni interpretate e implementazioni compilate in questi giorni?


7
@DeadMG Non è nuovo come si potrebbe pensare: una breve storia di just-in-time ...
yannis,

4
@DeadMG Dato che la maggior parte delle nuove lingue introdotte negli ultimi 10 anni o giù di lì funziona principalmente su un qualche tipo di VM, direi che ha ragione. Naturalmente ci sono ancora (e lo saranno per decenni a venire) le lingue compilate in codice nativo, e un JIT rimarrà di lusso (o no, se i ragazzi di PyPy si fanno strada). Quindi sì, possibile esagerazione, ma concordo sul fatto che il mainstream (per ora e il futuro prevedibile) sembra essere un compilatore bytecode + possibilmente JIT.

4
@DeadMG, devi avere una lunga barba bianca, se il modello VM è "nuovo" per te. P-codeera stato introdotto per la prima volta nel 1966. IBM Aix è in circolazione dal 1986.
Logica SK

6
Cose come shell unix, Tcl e simili verrebbero sempre interpretate in modo puramente, quindi la distinzione ha senso, almeno in un CS accademico. Ma è vero che quando i programmatori borbottano su interpreti e compilatori non hanno alcun senso nella maggior parte dei casi.
SK-logic,

3
@ SK-logic, penso che il tuo commento sia una risposta migliore di una qualsiasi delle risposte effettivamente pubblicate
Winston Ewert,

Risposte:


23

È importante ricordare che l'interpretazione e la compilazione non sono solo alternative l'una all'altra. Alla fine, qualsiasi programma che scrivi (incluso uno compilato in codice macchina) viene interpretato. Interpretare il codice significa semplicemente prendere una serie di istruzioni e restituire una risposta.

Compilare, d'altra parte, significa convertire un programma in una lingua in un'altra lingua. Di solito si presume che quando viene eseguita la compilazione, il codice viene compilato in un linguaggio di "livello inferiore" (ad es. Codice macchina, una sorta di bytecode VM, ecc.). Questo codice compilato viene ancora interpretato in seguito.

Per quanto riguarda la tua domanda se vi sia un'utile distinzione tra linguaggi interpretati e compilati, la mia opinione personale è che tutti dovrebbero avere una comprensione di base di ciò che sta accadendo al codice che scrivono durante l'interpretazione. Quindi, se il loro codice viene compilato JIT, o bytecode nella cache, ecc., Il programmatore dovrebbe almeno avere una conoscenza di base di ciò che ciò significa.


3
Sì, il programmatore dovrebbe avere una conoscenza di base. Ma mi chiedo se la terminologia compilata / interpretata non interferisca.
Winston Ewert,

2
Grazie!! Interpretato è solo un sinonimo di "eseguito", ed è così che vengono eseguiti tutti i programmi.
gardenhead,

9

La distinzione è profondamente significativa perché le lingue compilate limitano la semantica in modi che le lingue interpretate non necessariamente. Alcune tecniche interpretative sono molto difficili (praticamente impossibili) da compilare.

Il codice interpretato può fare cose come generare codice in fase di esecuzione e dare visibilità a quel codice in associazioni lessicali di un ambito esistente. Questo è un esempio. Un altro è che gli interpreti possono essere estesi con un codice interpretato che può controllare come viene valutato il codice. Questa è la base per gli antichi "fexprs" di Lisp: funzioni che vengono chiamate con argomenti non valutati e decidono cosa farne (avere pieno accesso all'ambiente necessario per percorrere il codice e valutare le variabili, ecc.). Nei linguaggi compilati, non puoi davvero usare quella tecnica; usi invece le macro: funzioni che vengono chiamate in fase di compilazione con argomenti non valutati e traducono il codice anziché interpretarlo.

Alcune implementazioni del linguaggio sono costruite attorno a queste tecniche; i loro autori rifiutano la compilazione come un obiettivo importante e preferiscono abbracciare questo tipo di flessibilità.

L'interpretazione sarà sempre utile come tecnica per avviare un compilatore. Per un esempio concreto, guarda CLISP (un'implementazione popolare di Common Lisp). CLISP ha un compilatore che è scritto in sé. Quando si crea CLISP, quel compilatore viene interpretato durante le prime fasi di costruzione. Viene utilizzato per compilare se stesso, quindi una volta compilato, la compilazione viene quindi eseguita utilizzando il compilatore compilato.

Senza un kernel interprete, avresti bisogno di avviare bootstrap con alcuni Lisp esistenti, come fa SBCL.

Con l'interpretazione, è possibile sviluppare un linguaggio da zero, iniziando con il linguaggio assembly. Sviluppa l'I / O di base e le routine di base, quindi scrivi un linguaggio di valutazione ancora fermo. Una volta che hai valutato, scrivi nella lingua di alto livello; il kernel del codice macchina esegue la valutazione. Utilizzare questa funzione per estendere la libreria con molte più routine e scrivere anche un compilatore. Utilizzare il compilatore per compilare quelle routine e il compilatore stesso.

Interpretazione: un importante trampolino di lancio nel percorso che porta alla compilazione!


1
IMO, questa è la risposta migliore. Sto lavorando sul mio linguaggio giocattolo e l'ultimo paragrafo descrive il modo in cui lo sto sviluppando. Rende davvero molto più semplice lavorare su nuove idee. Anche +1 per menzionare il processo bootstrap di CLISP.
sinan,

In teoria, qualsiasi linguaggio "interpretato" può essere trasformato in un linguaggio "compilato" generando un file EXE costituito dall'interprete più il codice sorgente o bytecode per il programma interpretato. Potrebbe non essere molto efficiente, però.
dan04,

Leggi come Wirth et al hanno inventato il codice P per semplificare il porting di PASCAL su nuove architetture di macchine. Questo è stato nei primi anni '70.
John R. Strohm,

1
Sospetto che il tuo paragrafo di apertura stia confondendo la compilazione e l'interpretazione con comportamenti statici e dinamici, ma ti darò il vantaggio del dubbio e chiederò solo un esempio di un linguaggio con semantica che è "praticamente impossibile" da compilare. Per quanto riguarda il bootstrap di un compilatore, è vero che la prima implementazione deve essere scritta in qualcosa di diverso dalla lingua che stai implementando, ma non deve essere un interprete, potrebbe essere un compilatore scritto in un'altra lingua.
8bittree

1

In realtà un sacco di implementazioni di lingue sono ancora interpretate rigorosamente, potresti non esserne consapevole. Per citarne alcuni: i linguaggi della shell UNIX, le shell cmd e PowerScript di Windows, Perl, awk, sed, MATLAB, Mathematica e così via.


3
Credo che Perl sia compilato internamente in bytecode e che almeno Mathematica possa essere compilata. E nulla impone l'implementazione di awk e sed (credo che alcuni dei coreutils GNU compilino espressioni regolari in automi finiti prima dell'esecuzione, il che probabilmente li metterebbe nella categoria "compilare per rappresentazione intermedia, interpretare quella").

1
In realtà sono abbastanza sicuro che Perl, MATLAB, Mathematica si compilino in bytecode. Non ho familiarità con PowerScript, vuoi dire Powershell? In tal caso, l'utilizzo del CLR e l'utilizzo del bytecode.
Winston Ewert,

@ WinstonEwert, scusa se volevo dire PowerShell. La mia comprensione è che la traduzione in una forma intermedia non significa che qualcosa non sia interpretato. Diamine, l'interprete BASIC originale di Dartmouth ha tradotto la fonte in token prima di interpretarla. Ciascuno degli strumenti che ho citato ha una modalità in cui 1) legge una riga di origine, 2) traduce quella riga in una forma eseguibile (possibilmente un codice intermedio anziché un codice nativo), 3) esegue il codice per quella riga, 4) ritorna a 1). Ciò corrisponde alla mia comprensione di un interprete.
Charles E. Grant,

2
Il bytecode implica la compilazione. Un compilatore bytecode è semplicemente un programma che prende il sorgente e lo converte in bytecode. Quindi tutti gli usi del bytecode devono coinvolgere un compilatore bytecode. Ma il bytecode deve anche essere interpretato (o JITted). Quindi qualsiasi cosa che usi bytecode è un ibrido interprete / compilatore.
Winston Ewert,

4
In realtà, la mia cosa è che le persone lanciano dichiarazioni come "Python è interpretato" e "Java è compilato" senza alcuna comprensione delle implementazioni. Sto chiedendo se sia anche utile descrivere un'implementazione in questi termini. Di solito la verità è più complicata e non è utile cercare di ridurla a interpretata / compilata.
Winston Ewert,

1

Penso: assolutamente sì .

In effetti, la maggior parte delle implementazioni converge sulla stessa strategia di base

In realtà, C ++ mira a trasferire sul dominio del compilatore un concetto di alto livello che di solito viene consegnato agli interpreti, ma rimane dalla parte delle minoranze ...


2
Attendi che Clang + LLVM diventi la toolchain del compilatore più popolare.
SK-logic,

@ SK-logic, nonostante il nome, credo che Clang + LLVM produca codice nativo.
Winston Ewert,

1
@ Winston Ewert, solo se lo desideri. Puoi fermarti al livello IR LLVM e fare tutto ciò che vuoi con esso - interpretalo, compila JIT, strumentalo come preferisci. Puoi persino tradurlo in Javascript e poi passare attraverso un interprete: github.com/kripken/emscripten/wiki
SK-logic

@ SK-logic, roba ordinata! Non sapevo che LLVM potesse farlo.
Winston Ewert,

1
La bellezza di llvm è questa separazione deliberata tra front-end e back-end. E gli strumenti per manipolare il centro prima di prendere di mira il set di istruzioni. È possibile unire l'intero progetto in bytecode e quindi ottimizzare su tutto, con altri compilatori sarebbe necessario disporre di un singolo file sorgente o almeno uno che includa il suo percorso attraverso l'albero dei sorgenti in modo che il compilatore agisca su un unico sorgente combinato. Anche un set di strumenti in llvm è generico per tutti i target, non è necessario compilare per ciascun target, un compilatore si adatta a tutti (almeno all'asm del target).
old_timer

-1

Distinzione utile: i programmi interpretati possono modificarsi aggiungendo o modificando le funzioni in fase di esecuzione.


8
Senza senso. Il codice di auto-modifica (macchina) è il trucco più antico del libro. Inoltre, alcuni sostengono che persino il codice nativo sia alla fine interpretato da un interprete cast in silicio (la CPU). Ma se assumiamo ciò, tutto il codice viene interpretato e non c'è distinzione da fare.

2
@delnan ha ragione. Aggiungerò semplicemente che i linguaggi moderni possono modificarsi creando nuove classi in modo dinamico e caricando / scaricando librerie (o "assembly" in .NET, ad esempio)
Jalayn,

5
Lisp comune viene compilato, ma è comunque possibile sostituire facilmente le definizioni delle funzioni in fase di runtime.
Logica SK

Questa è una caratteristica di interpretazione davvero interessante e necessaria (per esempio in Prolog).
CapelliC,
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.