Ich arbeite an meiner eigenen kleinen Programmiersprache für Bildungszwecke und bin auf ein kleines Problem gestoßen. Es gibt ein paar verschiedene Lösungen dafür, aber alle scheinen unelegant - und nach meinem Verständnis unnötig. Aber wenn ich die Bücher, die ich habe, und die Google-Suche durchlese, kann ich die elegante Lösung nicht finden.
Das Problem ist also, dass ich die grundlegende Lambda-Rechnung so aufbaue, wie ich sie verstehe. Ich habe wahr / falsch als Abstraktionsbegriffe definiert. Ich kann diese mit Funktionen kombinieren, um zu tun, ob / dann / sonst Verhalten. Das Problem kommt mit Schleifen. Ich kann eine grundlegende while-Schleife durch Rekursion definieren, aber in der Praxis führt dies zu einem Stapelüberlauf. Nach meinem Verständnis besteht die übliche Lösung darin, die Tail Call-Optimierung durchzuführen, aber ich sehe nicht, wie ich das kann - Bedingungen werden in der Sprache definiert. Aus diesem Grund weiß der Compiler nicht , dass sich der Körper der while-Schleife in der Endposition befindet.
Das Drachenbuch konzentriert sich auf die Implementierung der Schleife, vorausgesetzt, es gibt Labels und Gotos. Das könnte ich sicher tun. Es sieht so aus, als ob andere Sprachen, die keine Schleifenkonstrukte enthalten, zumindest Bedingungen einbauen und dann TCO ausführen. Und das könnte ich natürlich auch. Mein Verständnis ist jedoch, dass, solange ich Abstraktionen anwenden und Reduktionen durchführen kann, Schleifen (und alles andere) aus diesen Grundblöcken erstellt werden können sollten.
Also, was vermisse ich? Oder ist dies einer der Fälle, in denen "Sie können alles modellieren, wenn Sie X und Y haben" nicht dasselbe ist wie "Sie können alles modellieren, wenn Sie X und Y auf einem echten Computer haben" und integrierte Funktionen für die Praxis erforderlich sind Zwecke?
quelle
Antworten:
Also habe ich es heute geschafft, dieses Problem zu lösen. Der Code für meine while-Schleife:
Wenn ich dies in CIL einbaue (eine stapelbasierte Laufzeit, wichtig für den Pseudocode, nicht wichtig für die Antwort), sieht es so aus:
Das Wichtige, was mir gefehlt hat, ist, dass im
while
Code die Bedingung das Ding in der Schwanzposition war. Aus Sicht des Compilers sind der Block und die while-Funktion zwei separate Funktionen mit zwei separaten "Tails". Jedes von diesen kann leicht auf seine Heckposition hin bewertet werden, wodurch die Optimierung trotz des Fehlens integrierter Bedingungen realisierbar ist.quelle
Ich denke, Sie vermissen den Begriff der Fortsetzung . Obwohl sich Ihr Compiler möglicherweise nicht auf diesen Begriff verlässt, ist es als Compiler-Designer mit einer funktionalen Sprache als Quell- oder Zwischen- (oder Ziel-) Sprache wichtig, diesen Begriff zu verstehen und dies zu berücksichtigen.
Die Fortsetzung eines Codeteils beschreibt, worauf der Code hinausläuft. Imperativ ausgedrückt verkörpert es nicht nur den Ort, an den der Code springt oder fällt, sondern auch den Programmstatus (Stapel und Heap) an diesem Punkt. In der Lambda-Rechnung ist die Fortsetzung eines Subterms der Kontext, in dem es bewertet wird.
Wenn Sie imperativen Code in eine funktionale Sprache übersetzen, besteht eine der Schwierigkeiten darin, mit Code umzugehen, der auf verschiedene Arten beendet werden kann. Beispielsweise kann Code eine Ausnahme zurückgeben oder auslösen. Oder der Körper einer Schleife kann entweder die Bedingung erneut überprüfen oder die Schleife vollständig verlassen (
break
Konstrukt). Es gibt zwei Möglichkeiten, um damit umzugehen:Continue | Break
.Im Stil der Fortsetzung,
wird übersetzt in
Bei der Übersetzung eines Programms in eine typische imperative Sprache im Continuation-Passing-Stil ist die Fortsetzung immer das Letzte, was ein Code ausführt. Zum Beispiel wird die Fortsetzung von
body
oben nach dem gesamten Code von ausgeführtbody
, so dass die Tail-Call-Optimierung dazu führt, dass alle lokalen Variablenbody
unmittelbar vor dem Ausführen der Fortsetzung freigegeben werden.Einige Sprachen bieten erstklassige Fortsetzungen mit einem Konstrukt wie Call-with-Current-Continuation . Call / cc ist im Allgemeinen nicht für die Tail-Call-Optimierung geeignet - es kann in der Tat eine ziemlich teure Operation sein, da dies dazu führen kann, dass der gesamte Programmstatus dupliziert wird.
quelle