Die Sache ist, es gibt wirklich nicht viel Spielraum in Bezug auf die Funktionscodierung. Hier sind die Hauptoptionen:
Umschreiben von Begriffen: Sie speichern Funktionen als abstrakte Syntaxbäume (oder deren Codierung). Wenn Sie eine Funktion aufrufen, durchlaufen Sie den Syntaxbaum manuell, um seine Parameter durch das Argument zu ersetzen. Dies ist einfach, aber zeitlich und räumlich äußerst ineffizient .
Abschlüsse: Sie haben eine Möglichkeit, eine Funktion darzustellen, möglicherweise einen Syntaxbaum, wahrscheinlicher Maschinencode. Und in diesen Funktionen verweisen Sie in irgendeiner Weise auf Ihre Argumente. Es könnte ein Zeiger-Offset sein, es könnte eine Ganzzahl oder ein De Bruijn-Index sein, es könnte ein Name sein. Dann stellen Sie eine Funktion als Abschluss dar : die Funktion "Anweisungen" (Baum, Code usw.) gepaart mit einer Datenstruktur, die alle freien Variablen der Funktion enthält. Wenn eine Funktion tatsächlich angewendet wird, weiß sie irgendwie, wie die freien Variablen in ihrer Datenstruktur mithilfe von Umgebungen, Zeigerarithmetik usw. nachgeschlagen werden.
Ich bin sicher, dass es andere Optionen gibt, aber dies sind die grundlegenden, und ich vermute, dass fast jede andere Option eine Variante oder Optimierung der grundlegenden Verschlussstruktur sein wird.
In Bezug auf die Leistung sind Schließungen also fast überall besser als das Umschreiben von Begriffen. Welche der Variationen ist besser? Das hängt stark von Ihrer Sprache und Architektur ab, aber ich vermute, dass der "Maschinencode mit einer Struktur, die freie Variablen enthält", am effizientesten ist. Es hat alles, was die Funktion benötigt (Anweisungen und Werte) und nichts weiter, und das Aufrufen führt nicht zu großen Durchläufen.
Ich interessiere mich sowohl für den aktuellen Codierungsalgorithmus, den beliebte Funktionssprachen (Haskell, ML) verwenden
Ich bin kein Experte, aber ich bin zu 99% der Meinung, dass die meisten ML-Geschmacksrichtungen eine Variation der von mir beschriebenen Verschlüsse verwenden, obwohl einige Optimierungen wahrscheinlich sind. Sehen Sie dies für eine (möglicherweise veraltete) Perspektive.
Haskell macht etwas komplizierter wegen der verzögerten Auswertung: Es verwendet Spineless Tagless Graph Rewriting .
und auch in der effizientesten, die erreicht werden kann.
Was ist am effizientesten? Es gibt keine Implementierung, die für alle Eingaben am effizientesten ist. Sie erhalten also Implementierungen, die im Durchschnitt effizient sind, aber jede zeichnet sich in unterschiedlichen Szenarien aus. Es gibt also keine eindeutige Rangfolge der meisten oder am wenigsten effizienten.
Hier gibt es keine Magie. Um eine Funktion zu speichern, Sie müssen ihre freien Werte speichern irgendwie, sonst sind Sie kodieren weniger Informationen als die Funktion selbst hat. Vielleicht können Sie einige der freien Werte durch teilweise Auswertung optimieren, aber das ist riskant für die Leistung, und Sie müssen vorsichtig sein, um sicherzustellen, dass dies immer anhält.
Und vielleicht können Sie eine Art Komprimierung oder einen cleveren Algorithmus verwenden, um Raumeffizienz zu erzielen. Aber dann tauschen Sie entweder Zeit gegen Raum oder Sie befinden sich in einer Situation, in der Sie für einige Fälle optimiert und für andere langsamer geworden sind.
Sie können für den gemeinsamen Fall optimieren, aber der gemeinsame Fall , was sind auf der Sprache ändern, Einsatzgebiet, usw. Die Art des Code, der für ein Videospiel schnell ist (Anzahl, enge Schleifen mit großen Eingängen Knirschen) sind wahrscheinlich anders als Was ist schnell für einen Compiler (Baumdurchläufe, Arbeitslisten usw.)?
Bonuspunkt: Gibt es eine solche Codierung, die funktionscodierte Ganzzahlen nativen Ganzzahlen (kurz, int usw. in C) zuordnet? Ist es überhaupt möglich?
Nein das ist nicht möglich. Das Problem ist, dass Sie mit dem Lambda-Kalkül keine Begriffe überprüfen können. Wenn eine Funktion ein Argument mit demselben Typ wie eine Kirchenzahl verwendet, muss sie es aufrufen können, ohne die genaue Definition dieser Zahl zu untersuchen. Das ist die Sache mit den Kodierungen der Kirche: Das einzige, was Sie damit machen können, ist, sie anzurufen, und Sie können alles Nützliche damit simulieren, aber nicht ohne Kosten.
Noch wichtiger ist, dass die Ganzzahlen jede mögliche binäre Codierung belegen. Wenn also Lambdas als ihre ganzen Zahlen dargestellt würden, hätten Sie keine Möglichkeit, nicht kirchliche Lambdas darzustellen! Oder Sie würden eine Flagge einführen, um anzuzeigen, ob ein Lambda eine Ziffer ist oder nicht, aber dann ist wahrscheinlich jede gewünschte Effizienz aus dem Fenster verschwunden.
EDIT: Seit ich dies schreibe, ist mir eine dritte Option für die Implementierung von Funktionen höherer Ordnung bekannt geworden: die Defunktionalisierung . Hier wird jeder Funktionsaufruf zu einer großen switch
Aussage, je nachdem, welche Lambda-Abstraktion als Funktion angegeben wurde. Der Nachteil hierbei ist, dass es sich um eine vollständige Programmtransformation handelt: Sie können Teile nicht separat kompilieren und dann auf diese Weise miteinander verknüpfen, da Sie den vollständigen Satz von Lambda-Abstraktionen im Voraus benötigen.