2013-01-04 5 views
6

Ich möchte Funktionen höherer Ordnung (HOFs) in C als syntaktischen Zucker mit minimalem Aufwand implementieren. Zum Beispiel für den folgenden CodeFunktionen höherer Ordnung in C als syntaktischer Zucker mit minimalem Aufwand

function add(int x) { 
    return int(int y) { 
    return x + y; 
    }; 
} 

int main() { 
    function add1 = add(1); 
    return add1(2); 
} 

es in reines C als

transcompiled ist
#include <stdlib.h> 

typedef struct { 
    void *ctx; 
    void* (*fun)(void *arg, void *ctx); 
} function; 

function new_function(void *ctx, void* (*fun)(void *, void *)) { 
    function f = {.ctx=ctx, .fun=fun}; 
    return f; 
} 

void* apply(function f, void *arg) { 
    return (*(f.fun))(arg, f.ctx); 
} 

typedef struct { 
    int x; 
} context$1; 

void* new_context$1(int x) { 
    context$1 *ctx = malloc(sizeof(context$1)); 
    ctx->x = x; 
    return ctx; 
} 

void* function$1(void *arg, void *ctx) { 
    int y = (int)arg; 
    int x = ((context$1*)ctx)->x; 
    return (void*)(x + y); 
} 

function add(int x) { 
    return new_function(new_context$1(x), function$1); 
} 

int main() { 
    function add1 = add(1); 
    return (int)apply(add1, (void*)2); 
} 

Ich habe diese (manuell) laufen transcompiled Version und es funktioniert gut. Für die Implementierung glaube ich, dass einige AST-Manipulation und Lambda-Anhebung ausreichen würden.

Gibt es mögliche Fehler in meinem Ansatz? Gibt es leichtere Methoden für HOFs, oder kann ich meinen Ansatz verbessern, um die Implementierung zu erleichtern?

Antwort

2

Es gibt zwei offensichtliche Probleme jetzt:

  • Mit void * Parameter darstellen und Rückgabewerte jeglicher Art wird schließlich brechen. Betrachten Sie "long long" -Parameter auf ia-32 oder jede Struktur, die als Wert übergeben wird.
  • Es ist schwer (wenn möglich), Funktionen höherer Ordnung ohne Garbage Collection sinnvoll zu machen. Sie können es von Ihrem eigenen Beispiel sehen, wo Kontext $ 1 malloc'ed aber nie frei ist. Boehm GC kann hier helfen.
+0

Ich stimme völlig mit Ihrem zweiten Punkt und +1 für Boehm GC :-). Aber könntest du mir bitte beibringen, wie nichtig * irgendwann brechen würde?Ich glaube, sie würden gut funktionieren, weil sie automatisch generiert werden. –

+0

@XiaoJia: Das Problem mit 'void *' liegt in 'function $ 1'. Was ist, wenn der Typ von x + y größer als void ist? In diesem Beispiel würde das bedeuten "sizeof (int)> sizeof (void *)", was eine seltsame C-Implementierung wäre, aber nicht so seltsam, wenn 'x' den Typ' long long' anstelle von 'int' hätte. Dann verliert der Cast zu "void *" Informationen. –

+0

Nun, Sie können immer sagen, dass Ihr automatischer Übersetzer sich irgendwie darum kümmern wird (solange Sie den Algorithmus nicht beschreiben, sondern nur eine Beispielübersetzung liefern). Aber ich schaue nur auf (void *) 2 und sehe, dass es Informationen verlieren würde, wenn es einen breiteren als Zeiger-Wert anstelle von 2 gibt. (Und das Umwandeln nach/aus void * funktioniert überhaupt nicht für Strukturen, die nach Wert übergeben werden; Lehnen Sie es ab, sie überhaupt zu unterstützen, oder werden Sie solche Funktionen anders übersetzen? –

1

Beachten Sie, dass Ihr generierter Code undefiniertes Verhalten aufruft. Sie konvertieren an mehreren Stellen zwischen Ganzzahlarten und Zeigertypen über die direkte Besetzung. Dies ist in C nicht zulässig. Sie können nicht garantieren, dass ein Zeiger und eine int die gleiche Größe haben oder dass Sie sogar zwischen ihnen wechseln können, ohne den Wert zu ändern oder zu korrumpieren. Der Code funktioniert möglicherweise zufällig auf Ihrem bestimmten System, aber es wird auf vielen anderen Systemen unterbrochen.

Die einzige Möglichkeit, wie Sie sowohl Zeiger als auch Integraltypen (und auch Strukturen) generisch behandeln können, besteht darin, Parameter mit Variablenlängenlisten zu übergeben. Auf diese Weise können Sie jedes Argument unabhängig von der Größe des zugrunde liegenden Datentyps extrahieren.

Eine andere Möglichkeit besteht darin, die generische Struktur function zu löschen und eine benutzerdefinierte Struktur für jeden HOF im Code zu erstellen. Der Funktionszeiger könnte dann alle erforderlichen Parameter direkt auflisten, anstatt zu versuchen, sie hinter einer generischen Schnittstelle zu abstrahieren, wodurch die problematischen Umwandlungen eliminiert würden und der Code unabhängig von den verwendeten Datentypen wie erwartet funktionieren würde.

Soweit es die Benutzerfreundlichkeit betrifft, sollten Sie Ihre Schnittstelle so ändern, dass der Rückgabetyp des HOF (oder mindestens aufgeführt) in der Zeile angegeben wird, für die er deklariert ist. C-Objekte listet ihren Typ immer in der Deklaration auf. In Ihrem Beispiel lautet die Deklaration . Um herauszufinden, welchen Datentyp diese Funktion zurückgibt, müssen Sie die Definition für den HOF durchgehen. Dies ist keine schwierige Aufgabe für Ihren Beispielcode, aber dies könnte für komplexeren Code nicht trivial sein. Sie haben vielleicht keinen Code für den HOF, wenn er aus einer Bibliothek kommt. Vielleicht könnte etwas wie function(int) add1 = add(1); expliziter sein.

Als Randnotiz empfehle ich, dass Sie automatisch generierte Funktionen wie static definieren, um Namenskollisionen zwischen Modulen zu verhindern.

+0

Sehr hilfreicher Kommentar. Vielen Dank! Ich werde versuchen, meine Strategie für die Übersetzung zu aktualisieren. –

Verwandte Themen