Die Unterscheidung zwischen interpretiertem und kompiliertem Code ist wahrscheinlich eine Fiktion, wie Raphaels Kommentar unterstreicht :
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 ...
Tatsache ist, dass Code immer von der Software, der Hardware oder einer Kombination aus beiden interpretiert wird und der Kompilierungsprozess nicht erkennen kann, um welchen Code es sich handelt.
Was Sie als Zusammenstellung empfinden, ist ein Übersetzungsprozess von einer Sprache (für die Quelle) in eine andere Sprache (für das Ziel). Und der Interpreter für ist in der Regel unterscheidet sich von dem Interpreter für .STST
Das kompilierte Programm wird von einer syntaktischen Form übersetzt auf einem anderen syntaktische Form , so dass die beabsichtigte Semantik der Sprachen gegeben und , und die gleiche Rechenverhalten, bis auf ein paar Dinge , die Sie in der Regel versuchen, Änderungen, möglicherweise zur Optimierung, wie Komplexität oder einfache Effizienz (Zeit, Raum, Oberfläche, Energieverbrauch). Ich versuche, nicht von funktionaler Äquivalenz zu sprechen, da dies genaue Definitionen erfordern würde.PSPTSTPSPT
Einige Compiler wurden lediglich verwendet, um die Größe des Codes zu verringern und nicht, um die Ausführung zu "verbessern". Dies war der Fall für die Sprache, die im Plato-System verwendet wurde (obwohl sie es nicht als Kompilieren bezeichnet haben).
Sie können Ihren Code als vollständig kompiliert betrachten, wenn Sie nach dem Kompilieren den Interpreter für nicht mehr benötigen . Zumindest ist dies die einzige Möglichkeit, Ihre Frage als technische und nicht als theoretische Frage zu lesen (da ich den Interpreter theoretisch immer neu erstellen kann).S
Eine Sache, die ein Problem aufwerfen könnte, ist die Meta-Zirkularität . Dann manipuliert ein Programm syntaktische Strukturen in seiner eigenen Quellsprache und erzeugt Programmfragmente, die dann so interpretiert werden, als wären sie Teil des ursprünglichen Programms gewesen. Da Sie als Ergebnis einer willkürlichen Berechnung, die bedeutungslose syntaktische Fragmente manipuliert , beliebige Programmfragmente in der Sprache erzeugen können , würde ich vermuten, dass Sie es (aus technischer Sicht) nahezu unmöglich machen können, das Programm in die Sprache zu kompilieren , so dass es werden nun Fragmente von . Daher wird der Interpreter für benötigt, oder zumindest der Compiler von bisSSTTSST für das schnelle Kompilieren generierter Fragmente in (siehe auch dieses Dokument ).S
Ich bin mir aber nicht sicher, wie dies richtig formalisiert werden kann (und habe momentan keine Zeit dafür). Und unmöglich ist ein großes Wort für ein Thema, das nicht formalisiert ist.
Weitere Bemerkungen
Nach 36 Stunden hinzugefügt. Möglicherweise möchten Sie diese sehr lange Fortsetzung überspringen.
Die vielen Kommentare zu dieser Frage zeigen zwei Sichtweisen auf das Problem: eine theoretische Sichtweise, die es für bedeutungslos hält, und eine technische Sichtweise, die sich leider nicht so leicht formalisieren lässt.
Es gibt viele Möglichkeiten, Interpretation und Zusammenstellung zu betrachten, und ich werde versuchen, einige davon zu skizzieren. Ich werde versuchen, so ungezwungen wie möglich zu sein
Das Tombstone-Diagramm
Eine der frühen Formalisierungen (Anfang der 1960er bis Ende der 1990er Jahre) sind die T- oder
Tombstone-Diagramme . Diese Diagramme zeigen in zusammensetzbaren grafischen Elementen die Implementierungssprache des Interpreters oder Compilers, die zu interpretierende oder zu kompilierende Quellsprache und bei Compilern die Zielsprache. Ausgefeiltere Versionen können Attribute hinzufügen. Diese grafischen Darstellungen können als Axiome, Folgerungsregeln angesehen werden, die zur mechanischen Ableitung der Prozessorgenerierung aus einem Beweis ihrer Existenz aus den Axiomen à la Curry-Howard verwendet werden können (obwohl ich nicht sicher bin, ob dies in den sechziger Jahren geschehen ist :).
Teilbewertung
Eine weitere interessante Sichtweise ist das partielle Evaluierungsparadigma . Ich betrachte Programme einfach als eine Art Funktionsimplementierung, die bei bestimmten Eingabedaten eine Antwort berechnet. Dann ist ein Interpreter
für die Sprache ein Programm, das ein
in geschriebenes Programm und Daten für dieses Programm nimmt und das Ergebnis gemäß der Semantik von berechnet . Die partielle Auswertung ist eine Technik zum Spezialisieren eines Programms aus zwei Argumenten und , wenn nur ein Argument, beispielsweise , bekannt ist. Die Absicht ist eine schnellere Auswertung, wenn Sie endlich das zweite Argument erhaltenISSpSSdSa1a2a1a2 . Es ist besonders nützlich, wenn sich häufiger ändert als da die Kosten für die Teilbewertung mit bei allen Berechnungen, bei denen sich nur ändert , amortisiert werden können .a2a1a1a2
Dies ist eine häufige Situation beim Algorithmus-Design (häufig das Thema des ersten Kommentars zu SE-CS), wenn ein statischerer Teil der Daten vorverarbeitet wird, sodass die Kosten für die Vorverarbeitung bei allen Anwendungen amortisiert werden können des Algorithmus mit mehr variablen Teilen der Eingabedaten.
Dies ist auch die eigentliche Situation von Interpreten, da das erste Argument das auszuführende Programm ist und normalerweise mehrmals mit unterschiedlichen Daten ausgeführt wird (oder wenn Unterteile mehrmals mit unterschiedlichen Daten ausgeführt werden). Daher ist es naheliegend, einen Interpreter für eine schnellere Bewertung eines bestimmten Programms zu spezialisieren, indem er dieses Programm teilweise als erstes Argument bewertet. Dies kann als ein Weg zur Kompilierung des Programms angesehen werden, und es wurden umfangreiche Forschungsarbeiten zur Kompilierung durch partielle Bewertung eines Interpreters für sein erstes (Programm-) Argument durchgeführt.
Der Satz von Smn
Das Schöne am partiellen Bewertungsansatz ist, dass er seine Wurzeln in der Theorie hat (obwohl die Theorie ein Lügner sein kann), insbesondere im
Smn-Theorem von Kleene . Ich versuche hier, eine intuitive Darstellung davon zu geben, in der Hoffnung, dass es reine Theoretiker nicht verärgert.
Bei einer Gödel-Nummerierung von rekursiven Funktionen können Sie als Ihre Hardware anzeigen , sodass bei einer Gödel-Nummer
(gelesener Objektcode ) eines Programms die durch definierte Funktion ist (dh durch den Objektcode berechnet) Ihre Hardware).φφpφpp
In seiner einfachsten Form ist der Satz in Wikipedia wie folgt angegeben (bis auf eine kleine Änderung in der Notation):
Bei einer Gödel-Nummerierung von rekursiven Funktionen gibt es eine primitive rekursive Funktion von zwei Argumenten mit der folgenden Eigenschaft: Für jede Gödel-Nummer einer partiell berechenbaren Funktion mit zwei Argumenten werden die Ausdrücke und sind für die gleichen Kombinationen von natürlichen Zahlen und , und ihre Werte sind für jede solche Kombination gleich. Mit anderen Worten gilt die folgende erweiterte Gleichheit von Funktionen für jedes :
& sgr; q f & phiv; & sgr; ( q , x ) ( y ) f ( x , y ) x y xφσqfφσ(q,x)(y)f(x,y)xyxφσ(q,x)≃λy.φq(x,y).
Nehmen wir nun als Interpreter , als Quellcode eines Programms und als Daten für dieses Programm, so können wir schreiben:
I S x p S yqISxpSydφσ(IS,pS)≃λd.φIS(pS,d).
φIS kann als die Ausführung des Interpreters
auf der Hardware angesehen werden, dh als eine Blackbox, die bereit ist, in der Sprache geschriebene Programme zu interpretieren .ISS
Die Funktion kann als eine Funktion angesehen werden, die den Interpreter für das Programm wie bei der Teilauswertung spezialisiert. Somit kann gesehen werden, dass die Gödel-Nummer einen Objektcode aufweist, der die kompilierte Version des Programms .σISPSσ(IS,pS)pS
Die Funktion kann als eine Funktion angesehen werden, die den Quellcode eines
in Sprache geschriebenen Programms als Argument nimmt und die Objektcodeversion für dieses Programm . So ist das, was in der Regel einen Compiler genannt wird.CS=λqS.σ((IS,qS)qSSCS
Einige Schlussfolgerungen
Wie gesagt: "Theorie kann ein Lügner sein" oder scheint es tatsächlich zu sein. Das Problem ist, dass wir nichts von der Funktion wissen . Tatsächlich gibt es viele solcher Funktionen, und ich vermute, dass der Beweis des Theorems eine sehr einfache Definition verwendet, die aus technischer Sicht vielleicht nicht besser ist als die von Raphael vorgeschlagene Lösung: einfach die zu bündeln Quellcode mit dem Interpreter . Dies ist immer möglich, so dass wir sagen können: Kompilieren ist immer möglich.q S I SσqSIS
Die Formalisierung einer restriktiveren Vorstellung von dem, was ein Compiler ist, würde einen subtileren theoretischen Ansatz erfordern. Ich weiß nicht, was in dieser Richtung getan worden sein könnte. Die sehr reale Arbeit an der Teilbewertung ist aus technischer Sicht realistischer. Und es gibt natürlich auch andere Techniken zum Schreiben von Compilern, einschließlich der Extraktion von Programmen aus dem Nachweis ihrer Spezifikation, wie sie im Kontext der Typentheorie auf der Grundlage des Curry-Howard-Isomorphismus entwickelt wurden (aber ich komme außerhalb meines Zuständigkeitsbereichs). .
Ich wollte damit zeigen, dass Raffaels Bemerkung nicht "verrückt" ist, sondern eine vernünftige Erinnerung daran, dass die Dinge nicht offensichtlich und nicht einmal einfach sind. Zu sagen, dass etwas unmöglich ist, ist eine starke Aussage, die genaue Definitionen und einen Beweis erfordert, wenn auch nur, um genau zu verstehen, wie und warum es unmöglich ist . Es kann jedoch recht schwierig sein, eine geeignete Formalisierung zu erstellen, um einen solchen Beweis zu erbringen.
Dies bedeutet, dass, selbst wenn ein bestimmtes Feature nicht kompilierbar ist, im Sinne der Ingenieure, Standardkompilierungstechniken immer auf Teile der Programme angewendet werden können, die ein solches Feature nicht verwenden, wie Gilles 'Antwort bemerkte.
Um Gilles 'Schlüsselbemerkungen zu folgen, dass abhängig von der Sprache einige Dinge zur Kompilierungszeit erledigt werden können, während andere zur Laufzeit erledigt werden müssen und daher spezifischen Code erfordern, können wir sehen, dass das Konzept der Kompilierung tatsächlich ist schlecht definiert und wahrscheinlich in keiner zufriedenstellenden Weise definierbar. Die Kompilierung ist nur ein Optimierungsprozess, wie ich im Teilauswertungsabschnitt gezeigt habe, als ich sie mit der statischen Datenvorverarbeitung in einigen Algorithmen verglichen habe.
Als komplexer Optimierungsprozess gehört der Begriff der Kompilierung tatsächlich zu einem Kontinuum. Abhängig von den Merkmalen der Sprache oder des Programms sind einige Informationen möglicherweise statisch verfügbar und ermöglichen eine bessere Optimierung. Andere Dinge müssen auf die Laufzeit verschoben werden. Wenn es wirklich schlimm wird, muss zumindest für einige Teile des Programms zur Laufzeit alles erledigt werden, und Sie können den Quellcode nur mit dem Interpreter bündeln. Diese Bündelung ist also nur das untere Ende dieses Kompilierungskontinuums. Bei der Forschung zu Compilern geht es vor allem darum, Wege zu finden, um statisch zu arbeiten, was früher dynamisch gemacht wurde. Die Garbage Collection zur Kompilierungszeit scheint ein gutes Beispiel zu sein.
Beachten Sie, dass die Aussage, dass der Kompilierungsprozess Maschinencode erzeugen sollte, keine Hilfe ist. Genau das kann die Bündelung bewirken, da es sich bei dem Interpreter um Maschinencode handelt (bei der Cross-Kompilierung kann es etwas komplexer werden).