Perché il primo compilatore è stato scritto prima del primo interprete?


72

Il primo compilatore è stato scritto da Grace Hopper nel 1952, mentre l'interprete Lisp è stato scritto nel 1958 dallo studente Steve Russell di John McCarthy. Scrivere un compilatore sembra un problema molto più difficile di un interprete. Se è così, perché il primo compilatore è stato scritto sei anni prima del primo interprete?


67
perché all'epoca c'era più bisogno di un compilatore che di un interprete
maniaco del cricchetto

23
Per compilare il primo interprete? : P (solo parzialmente nella guancia, un interprete è complesso (più che un semplice compilatore) e sarebbe brutto scriverne uno efficiente in codice macchina)
Valità,

4
Se prendi la tua CPU eseguendo le istruzioni come interpretandole, che è il mio modo di vederlo (comunque implementato via hardware), l'affermazione che il primo interprete è stato scritto dopo il primo compilatore non è valida. A proposito, so che questo non aiuta con la domanda / risposta. È solo pensato per essere un commento interessante su un diverso punto di vista.
Pedro Henrique A. Oliveira,

11
McCarthy non ha scritto l'interprete LISP. McCarthy ha inventato un formalismo matematico. Steve Russell, uno degli studenti del MIT AI Lab dell'epoca, si rese conto di poter scrivere un interprete per eseguire i voli fantasiosi di McCarthy, e il resto è storia.
John R. Strohm,

5
In effetti, ho sentito che McCarthy credeva che LISP fosse inattuabile. Fu Russell a rendersi conto che la funzione universale di McCarthy dal manuale LISP a) era un interprete eb) era implementabile.
Jörg W Mittag,

Risposte:


97

Scrivere un compilatore sembra un problema molto più difficile di un interprete.

Questo potrebbe essere vero oggi, ma direi che non era così circa 60 anni fa. Alcuni motivi per cui:

  • Con un interprete, devi tenere in memoria sia esso che il programma. In un'epoca in cui 1kb di memoria era un lusso enorme, era fondamentale mantenere basso il footprint della memoria corrente. E l'interpretazione richiede un po 'più di memoria rispetto all'esecuzione di un programma compilato.
  • Le moderne CPU sono estremamente complesse con enormi cataloghi di istruzioni. Quindi scrivere un buon compilatore è davvero una sfida. Le vecchie CPU erano molto più semplici, quindi anche la compilazione era più semplice.
  • Le lingue moderne sono molto più complesse delle vecchie lingue, quindi anche i compilatori sono molto più complessi. Le vecchie lingue avrebbero quindi compilatori più semplici.

65
e senza un compilatore dovresti scrivere l'interprete in assemblea
maniaco del cricchetto,

34
I primi compilatori in cui le persone. (Quando le persone sono interpreti non abbiamo bisogno dei computer). Quindi qualcuno deve aver pensato di scrivere un compilatore in un linguaggio compilato (gli unici che avevano, ma come il tempo ha compilato i miei umani).
ctrl-alt-delor,

1
In realtà .... Di recente ho letto che l'Apollo Guidance Computer utilizzato nei voli lunari degli anni '60 aveva un interprete: voce wiki "... l'interprete ha fornito molte più istruzioni di quelle supportate da AGC in modo nativo ..." Suppongo che questo "interprete" fosse più come l' interprete "Sweet 16" incorporato nei vecchi computer Apple II che qualcosa come LISP.
Calphool,

6
@ratchetfreak Ed è stato. Come sono stati i primi compilatori.
RBarryYoung,

3
@richard: quando le persone erano interpreti / calcolatrici, il loro titolo professionale era chiamato "computer". Quindi, quando le persone erano interpreti, erano letteralmente i computer. Il progetto Manhattan ha impiegato migliaia di "computer" (titolo di lavoro effettivo, sul serio) a cui i fisici nucleari hanno inviato i loro calcoli per posta per essere eseguiti. In realtà, il progetto ha anche impiegato matematici che hanno interrotto i calcoli degli ingegneri che hanno formulato i calcoli dalle teorie dei fisici per inviare i "computer".
Slebetman,

42

Il punto fondamentale è che l'ambiente hardware di elaborazione degli anni '50 lo rendeva tale da rendere possibile solo un compilatore, dato che all'epoca l'elaborazione dei computer era orientata al batch.

All'epoca le migliori interfacce utente erano principalmente limitate a schede perforate e stampanti per teletype . Nel 1961 il sistema SAGE divenne il primo display a tubo catodico (CRT) su un computer. Quindi la natura interattiva di un interprete non era preferibile o naturale fino a molto tempo dopo.

Numerosi computer negli anni '50 utilizzavano gli interruttori del pannello frontale per caricare le istruzioni, e l'output era semplicemente file di lampade / LED, e gli appassionati hanno persino usato interruttori e LED sul pannello anteriore negli anni '70. Forse hai familiarità con il famigerato Altair 8800 .

Anche altre limitazioni hardware hanno reso gli interpreti impossibili. C'era l'estrema disponibilità limitata della memoria primaria (ad es. RAM) nei computer negli anni '50. Prima del circuito integrato a semiconduttore (che non arrivò fino al 1958) la memoria era limitata alla memoria del nucleo magnetico o alla memoria della linea di ritardo misurata in bit o parole , nessun prefisso. In combinazione con la lentezza della memoria di archiviazione secondaria (ad es. Disco o nastro), sarebbe considerato dispendioso, se non impossibile, avere gran parte della memoria utilizzata per l'interprete, anche prima che il programma interpretato fosse caricato.

Le limitazioni di memoria erano ancora un fattore importante quando il team guidato da John Backus alla IBM creò il compilatore FORTRAN nel 1954-57. Questo compilatore innovativo ha avuto successo solo perché era un compilatore ottimizzante .

La maggior parte dei computer negli anni '50 aveva a malapena qualsiasi sistema operativo, per non parlare delle funzionalità moderne come il collegamento dinamico e la gestione della memoria virtuale, quindi l'idea di un interprete era troppo radicale e poco pratica in quel momento.

appendice

Le lingue degli anni '50 erano primitive. Hanno incluso solo un piccolo manipolo di operazioni, spesso influenzato sia da istruzioni del hardware sottostante o la definizione del problema del loro utilizzo mirato.

A quel tempo, i computer erano raramente computer di uso generale, nel senso che oggi pensiamo ai computer. Il fatto che fossero riprogrammabili senza dover essere ricostruiti era considerato un concetto rivoluzionario - in precedenza le persone avevano utilizzato macchine elettromeccaniche (in genere calcolatrici) per calcolare o calcolare le risposte (la maggior parte delle applicazioni negli anni '50 erano di natura numerica).

Dal punto di vista dell'informatica, i compilatori e gli interpreti sono entrambi traduttori e hanno quasi la stessa complessità da implementare.


I computer hanno usato i teletipi prima dell'avvento della condivisione del tempo? Mi sarei aspettato che usassero le stampanti di linea o lo spool su nastro. Altrimenti, i 1-8 secondi che il computer dovrebbe attendere dopo ogni riga di testo rappresenterebbe un 1-8 secondi che il computer avrebbe potuto - ma non stava facendo - fare qualcosa di utile.
supercat,

2
@supercat - sì, sono stati usati i teletipi, ma erano tutt'altro che "interattivi". Il primo computer che ho programmato aveva una memoria composta da parole a 18 bit, di cui 1K. La memoria stessa era un tamburo rotante , quindi quando accendevi il computer dovevi aspettare che la memoria aumentasse. Aveva un teletipo collegato e si poteva A) leggere un carattere digitato dalla tastiera o letto dal lettore di nastri di carta (dal punto di vista del computer entrambi erano uguali), B) scrivere un carattere nella "stampante "del teletipo. Ah, bei giorni - GRANDI giorni ..! IL MIO GRUEL È ANCORA CALDO?!?!? :-)
Bob Jarvis,

@BobJarvis: che anno sarebbe stato?
supercat

1
@supercat - 1973 - ma il computer in questione (un EDP-18, di Educational Data Products) era relativamente anziano anche allora. Tuttavia, era quello che avevamo (e avere qualsiasi computer con cui andare al liceo nella prima metà degli anni '70 era insolito), quindi pensavamo che fosse piuttosto sorprendente. :-)
Bob Jarvis,

2
Questa è una risposta molto migliore e completa di quella accettata.
Jim Garrison,

12

I primi linguaggi di programmazione erano abbastanza semplici (nessuna ricorsione per esempio) e vicini all'architettura della macchina che era essa stessa semplice. La traduzione fu quindi un processo semplice .

Un compilatore era più semplice come programma di un interprete che avrebbe dovuto tenere insieme i dati per l'esecuzione del programma e le tabelle per interpretare il codice sorgente. E l'interprete occuperebbe più spazio , per se stesso, per il codice sorgente del programma e per le tabelle simboliche.

La memoria potrebbe essere così scarsa (sia per motivi di costo che architetturali) che i compilatori potrebbero essere programmi autonomi che sovrascrivono il sistema operativo (ne ho usato uno di questi). Il sistema operativo doveva essere ricaricato dopo la compilazione per eseguire il programma compilato. ... il che rende chiaro che eseguire un interprete per un vero lavoro non era semplicemente un'opzione .

A dire il vero, la semplicità richiesta ai compilatori era tale che i compilatori non erano molto buoni (l'ottimizzazione del codice era ancora agli inizi, se considerata a tutti). Il codice macchina scritto a mano aveva, almeno fino alla fine degli anni sessanta in alcuni punti, la reputazione di essere significativamente più efficiente del codice generato dal compilatore. C'era persino un concetto di rapporto di espansione del codice , che confrontava la dimensione del codice compilato con il lavoro di un ottimo programmatore. Di solito era maggiore di 1 per la maggior parte (tutti?) Dei compilatori, il che significava programmi più lenti e, cosa molto più importante, programmi più grandi che richiedevano più memoria. Questo era ancora un problema negli anni sessanta.

L'interesse del compilatore era per la facilità di programmazione, in particolare per gli utenti che non erano specialisti di informatica, come scienziati in vari campi. Questo interesse non era la prestazione del codice. Tuttavia, il tempo del programmatore è stato quindi considerato una risorsa economica. Il costo era in tempo per computer, fino al 1975-1980, quando il costo passò dall'hardware al software. Ciò significa che anche alcuni compilatori non sono stati presi sul serio da alcuni professionisti .

L'altissimo costo del tempo al computer era un'altra ragione per squalificare gli interpreti , al punto che l'idea stessa sarebbe stata ridicola per la maggior parte delle persone.

Il caso di Lisp è molto speciale, perché era un linguaggio estremamente semplice che lo rendeva fattibile (e il computer era diventato un po 'più grande in 58). Ancora più importante, l'interprete di Lisp era una dimostrazione del concetto di auto definibilità di Lisp ( meta-circolarità ), indipendentemente da qualsiasi problema di usabilità.

Il successo di Lisp è dovuto in gran parte al fatto che questa auto-definibilità lo ha reso un eccellente banco di prova per lo studio delle strutture di programmazione e la progettazione di nuovi linguaggi (e anche per la sua convenienza per il calcolo simbolico).


3
Mio, quanto audace .
Pharap,

@Pharap È quello stile improprio? Il mio scopo è quello di facilitare la ricerca delle informazioni chiave, consentendo nel contempo uno stile più libero rispetto alla semplice enumerazione dei fatti. Devo confessare che in questo sito SE non sembra molto efficace.
babou,

@ anonymous-downvoter Il downvoting senza un commento esplicativo mostra che qualcuno può essere stupido. Tuttavia, non dice chi.
babou,

4

Non sono d'accordo con la premessa della domanda.

Adm. Il primo compilatore di Hopper (A-0) era più simile a un linker o a un linguaggio macro. Ha memorizzato le subroutine su un nastro (a ciascuna assegnata un numero) e i suoi programmi sarebbero stati scritti come un elenco di subroutine e argomenti. Il compilatore copiava le subroutine richieste dal nastro e le riordinava in un programma completo.

Ha usato la parola "compilare" nello stesso senso in cui si compila un'antologia di poesie: raccogliere vari oggetti insieme in un volume.

Quel primo compilatore non aveva un lexer o un parser, per quanto posso dire, il che lo rende un antenato distante di un compilatore moderno. In seguito ha creato un altro compilatore (il B-0, alias FLOW-MATIC) con l'obiettivo di una sintassi più simile all'inglese, ma non è stato completato fino al 1958 o al 1959, all'incirca nello stesso periodo dell'interprete Lisp.

Pertanto, penso che la domanda stessa sia un po 'sbagliata. Sembra che i compilatori e gli interpreti si siano evoluti quasi esattamente allo stesso tempo, senza dubbio a causa della condivisione di idee che molti scienziati avrebbero pensato sulla stessa linea in quei giorni.

Una risposta migliore con citazioni qui: https://stackoverflow.com/a/7719098/122763 .


3

L'altra parte dell'equazione è che i compilatori erano un'astrazione graduale sopra un assemblatore. Per prima cosa avevamo un codice macchina codificato. "Noi" eravamo l'assemblatore. Ogni salto, offset, ecc. Veniva calcolato a mano in esadecimale (o ottale) e quindi perforato in nastro di carta o carte. Quindi, quando sono arrivati ​​gli assemblatori, è stato un enorme risparmio di tempo. Il passo successivo sono stati gli assemblatori di macro. Ciò ha dato la possibilità di scrivere una macro che si espanderebbe in una serie di istruzioni della macchina. Quindi Fortran e Cobol hanno fatto un enorme passo avanti. La mancanza di risorse (archiviazione, memoria e cicli della CPU) significava che gli interpreti per scopi generici dovevano aspettare che la tecnologia crescesse. La maggior parte dei primi interpreti erano motori di codice byte (come Java o CLR di oggi, ma con capacità molto meno). UCSD Pascal era un linguaggio molto popolare (e veloce). MS Basic era un motore a codice byte sotto il cofano.

In termini di istruzioni generali, dipendeva totalmente dal processore in esecuzione. L'industria ha attraversato un grande tumulto RISC vs CISC per un po '. Ho scritto personalmente assemblatore per IBM, Data General, Motorola, Intel (quando si sono presentati), TI e molti altri. C'era una differenza abbastanza significativa nei set di istruzioni, nei registri, ecc. Che avrebbe influenzato ciò che era richiesto per "interpretare" un codice p.

Come riferimento temporale, ho iniziato a programmare nella compagnia telefonica intorno al 1972.


2

Se non si tiene tutto in memoria, il codice compilato è molto più rapido. Non dimenticare che in questi periodi le funzioni sono state unite al codice compilato. Se non stai compilando, non sai quali funzioni ti serviranno. Quindi, stai chiamando funzioni da ... Oh, non ancora dal disco, siamo nei primi 50 anni, ma dalle carte! In fase di esecuzione!

Naturalmente, è possibile trovare soluzioni alternative, ma non sono state ancora trovate, poiché le lingue erano molto semplici e non così lontane dal codice macchina. E la compilazione è stata semplice e sufficiente.


1

Prima che il primo compilatore fosse scritto, la gente scriveva il codice assembler che era un enorme progresso rispetto al semplice codice binario. A quel tempo, c'era una forte argomentazione secondo cui il codice compilato da un compilatore sarebbe meno efficiente del codice assembler - a quel tempo la relazione tra (costo del computer) e (costo del programmatore) era molto, molto diversa da quella odierna. Quindi c'era una forte resistenza contro i compilatori da quel punto di vista.

Ma i compilatori sono molto più efficienti degli interpreti. Se avessi suggerito di scrivere un interprete in quel momento, le persone avrebbero pensato che fossi pazzo. Riesci a immaginare di acquistare un computer da un milione di dollari e poi sprecare il 90% della sua capacità di interpretare il codice?


Non so molto sui computer degli anni '50, ma almeno per le semplici macchine von Neumann degli ultimi decenni, l'overhead dell'interprete sarebbe composto da due a cinque istruzioni (forse da quattro a dieci cicli in totale) per istruzione interpretata: decodifica, salto indiretto, forse decodifica argomenti aggiuntivi. Se si imposta l'istruzione interpretata su un livello sufficientemente alto (quindi si eseguono diverse istruzioni macchina per istruzione interprete), questo sarebbe inferiore al 90% e forse persino accettabile. Vedi anche: Forth.

3
Prima della stesura del primo compilatore, la maggior parte dei programmi era in realtà scritta nel vero codice macchina, non nel linguaggio assembly (codici op mnemonici).
mctylr,

2
@delnan Quei sistemi funzionavano con orologi nel kiloHertz, quindi sprecare una riduzione di 3-5 volte delle prestazioni sarebbe probabilmente la differenza tra il completamento del programma con successo prima che il sistema fallisca a causa di un guasto hardware (cioè un tubo a vuoto) o meno. Gli errori hardware erano un evento quotidiano negli anni '50 se ricordo bene.
mctylr,

delnan: mostrami un interprete in grado di interpretare con un sovraccarico di cinque istruzioni. E Forth non è un linguaggio interpretato, è un abominio :-). Scusa, intendo una lingua filettata. Qualcosa come BASIC richiede migliaia di istruzioni per interpretare un'affermazione.
gnasher729,

@gnasher: Nel 1974, ho creato un interprete BASIC ad alte prestazioni per Keronix (ahem, cloni Data General Nova) che utilizzava un codice P orientato al byte per codificare l'istruzione BASIC. Sono state necessarie circa 25-50 istruzioni macchina per "interpretare" un pCode, 10 delle quali per il byte stesso. (Ne ho fatto uno basato su token nel 1969 che ha richiesto di più perché doveva in effetti riesaminare le espressioni). Ma non era e non è "gazillions".
Ira Baxter,

1

Prima che un programma di looping possa essere interpretato, deve essere memorizzato in un supporto che può essere letto ripetutamente. Nella maggior parte dei casi, l'unico mezzo adatto sarebbe la RAM. Dato che il codice verrà tipicamente inserito su schede perforate che - per le lingue leggibili dall'uomo - sono probabilmente in gran parte vuote, è necessario eseguire una sorta di elaborazione sul codice prima che sia archiviato nella RAM. In molti casi, elaborare il testo della scheda perforata in un modulo adatto all'esecuzione diretta da parte del processore non è in realtà più difficile che elaborarlo in un modulo che può essere gestito in modo efficiente tramite un interprete.

Si noti che l'obiettivo dei primi compilatori non era quello di produrre un file di linguaggio assembly o di codice oggetto su disco, ma piuttosto di finire il codice nella RAM che era pronto per l'esecuzione. Questo è in realtà sorprendentemente facile quando non c'è nessun sistema operativo che si frapponga. Un compilatore può generare codice a partire da un'estremità della memoria e allocare variabili e destinazioni di diramazione a partire dall'altra. Se un'istruzione è contrassegnata con l'etichetta "1234", il compilatore memorizzerà nella variabile chiamata "1234" un'istruzione per passare all'indirizzo di generazione del codice corrente, creando quella variabile se non esiste. Un'istruzione "goto 1234" creerà una variabile "1234" se non esiste, quindi salterà a quella variabile [che si spera avrà un salto nella posizione corretta memorizzata prima dell'esecuzione dell'istruzione].gotoun'etichetta che non è stata ancora definita, dal momento che sa quando la gotocompilazione andrà a saltare - in una variabile. Potrebbe non essere il modo più efficiente per generare codice, ma è adeguato per le dimensioni dei programmi che i computer dovrebbero gestire.


1
Disco? Ummmm ... no. Nastro. Nastri grandi, lenti, bobina a bobina. E molti di loro. Ricordo di essere andato a una conferenza tenuta da Grace Murray Hopper (doppiamente interessante per me perché ero un esperto di analisi dei sistemi E un uomo di bordo nel distaccamento ROTC della Marina nel campus, e CAPT Hopper era un ufficiale di marina in servizio). Ha raccontato una storia in cui, ha detto, le è venuta l'idea di scrivere su nastro parti inutilizzate di un programma - lo ha chiamato "usando l'archiviazione ausiliaria". "Ma", ha detto, "l'idea non ha funzionato fino a quando IBM non ha fatto la stessa cosa e l'ha chiamata memoria virtuale". CAPT Hopper non amava davvero IBM ... :-)
Bob Jarvis,

@BobJarvis: non avevo considerato quell'aspetto, ma lo stesso design generale a un passaggio che sarebbe stato usato per compilare il codice pronto per l'esecuzione su RAM poteva funzionare bene con un'unità a nastro; l'output del compilatore andrebbe su nastro, quindi il nastro verrebbe riavvolto e letto in indirizzi di memoria sequenziali ed eseguito direttamente, senza la necessità di avere contemporaneamente parte del compilatore in memoria.
supercat

0

Perché gli interpreti hanno bisogno di compilatori per funzionare.

L'affermazione di cui sopra non è davvero vera. A rigor di termini, puoi fare un interprete senza mai usare o interagire in altro modo con un compilatore. Ma i risultati di questo non sarebbero molto simili a ciò che penso che intendi con questi termini.

In senso stretto, compilatori e interpreti fanno cose completamente diverse. Un compilatore legge il testo da una fonte e lo trasforma in un altro formato: linguaggio assembly, codice macchina, un altro linguaggio di alto livello, una struttura di dati o qualsiasi altra cosa. Un interprete, nel frattempo, accetta una struttura di dati di qualche tipo ed esegue istruzioni basate su di esso.

Ciò che tendiamo a pensare come un "compilatore" al giorno d'oggi è in realtà un compilatore che è stato accoppiato con un generatore di codice : un programma che accetta dati da una fonte e genera codice in un formato basato su ciò che vede. Questo è un uso abbastanza intuitivo per i compilatori ed è stata una delle prime cose per cui sono stati creati i compilatori. Ma se lo guardi in un altro modo, questo sembra molto simile a quello che fa un interprete. Emette sempre codice invece di eseguire operazioni più generali, ma il principio è lo stesso.

Se lo guardiamo dall'altra parte, un interprete deve ottenere i suoi dati da qualche parte . Questi sono solo dati, quindi puoi crearli come faresti con qualsiasi altro tipo di dati. Dal momento che stiamo parlando di interpretazione, sembra naturale che tu possa costruire i tuoi dati in base alle istruzioni in un file di testo. Ma per farlo, avresti bisogno di qualcosa da leggere nel testo e creare la tua struttura di dati, e questo è un compilatore . È collegato all'interprete anziché a un generatore di codice, ma è comunque un compilatore.

Ecco perché i compilatori sono stati scritti per primi. L'idea di interpretare le strutture di dati non era nuova anche quando furono concepiti i compilatori, ma i compilatori erano il "collegamento mancante" che permetteva ai programmatori di costruire quelle strutture dal testo.


L'interprete Basic originale per la Apple] [da quanto ho capito, è stato tradotto a mano in codice macchina e non è mai esistito in formato codice sorgente leggibile dalla macchina. Non sono sicuro di quale "compilatore" diresti sia stato usato per produrlo.
supercat

@supercat: Non so come sia stato prodotto l'interprete Basic di cui parli, ma non sto parlando di compilatori usati per produrre interpreti. Sto parlando dei compilatori che fanno parte degli interpreti. Come parte del suo codice, l'Apple] [l'interprete BASIC ha bisogno di un modo per leggere i file di testo contenenti il ​​codice BASIC e creare un albero di sintassi da quel testo, che quindi esegue. Il codice che fa questo è un compilatore; non genera codice macchina, ma esegue comunque le attività di lettura del codice e traduzione in un altro modulo.
The Spooniest,

I tipici interpreti BASIC del microcomputer non producono nulla che assomigli in remoto a un albero di sintassi. Non ho familiarità con l'interprete BASIC originale di Apple (chiamato "BASIC intero") ma l'interprete BASIC implementato da Microsoft per Apple ("Applesoft BASIC"), Commodore e varie altre società ha memorizzato il testo del programma così com'è, tranne due cose : (1) ogni riga era preceduta da un numero di riga a 16 bit e dall'indirizzo a 16 bit della riga successiva e seguita da un byte zero; (2) ogni parola chiave è stata sostituita con un singolo byte con il bit alto impostato. Oltre a ciò, le espressioni sono state analizzate ...
supercat

... carattere per carattere, da sinistra a destra; un'istruzione come A = 1234 "converte le cifre 1, 2, 3 e 4 in un numero a virgola mobile in fase di esecuzione.
supercat

@supercat: Sembra più vicino a un bytecode che a un albero di sintassi, quindi, quindi mi sbaglio su quel particolare. Ma il punto principale è ancora valido: lo pseudo-bytecode deve ancora essere costruito dal testo e il codice che lo fa è un compilatore.
The Spooniest,

-1

Un altro fattore: quando sono stati scritti i primi compilatori, il costo del tempo della macchina era molto più alto di quanto non sia ora. Gli interpreti utilizzano molto più tempo della macchina.

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.