2009-06-21 15 views
35

Angenommen, ich habe einen Zeiger auf eine Funktion _stack_push(stack* stk, void* el). Ich möchte in der Lage sein, curry(_stack_push, my_stack) aufrufen und eine Funktion zurück, die nur void* el dauert. Ich konnte mir keinen Weg dazu vorstellen, da C keine Laufzeitfunktionsdefinition erlaubt, aber ich weiß, dass es weit klügere Leute gibt als ich hier :). Irgendwelche Ideen?Gibt es eine Möglichkeit in C curry zu machen?

Antwort

19

fand ich eine Arbeit von Laurent Dami, die in C/C++/Objective-C bespricht currying:

More Functional Reusability in C/C++/Objective-c with Curried Functions

Von Interesse, wie es in C implementiert:

Unsere aktuellen Implementierung verwendet vorhandene C-Konstrukte, um den Curry-Mechanismus hinzuzufügen. Dies war viel einfacher als das Ändern des Compilers und reicht aus, um das Interesse des Currys zu beweisen. Dieser Ansatz hat jedoch zwei Nachteile. Erstens können Curry-Funktionen nicht typisiert werden und erfordern daher einen sorgfältigen Gebrauch, um Fehler zu vermeiden. Zweitens kann die Curry-Funktion die Größe ihrer Argumente nicht kennen und zählt sie so, als ob sie alle die Größe einer ganzen Zahl hätten.

Das Papier keine Implementierung von curry() enthält, aber man kann sich vorstellen, wie es umgesetzt wird function pointers und variadic functions verwenden.

+0

Sieht aus wie ein gutes Buch, danke! – Burke

+9

+1 großer Fund, und ich mag "Obwohl wir keine umfangreichen Tests ausgeführt haben, können wir schätzen, dass ein Curry-Funktionsaufruf ungefähr 60 Mal langsamer ist als ein normaler Funktionsaufruf." –

+5

(Ich mag es, weil manchmal Sie etwas sehr schlecht brauchen, und eine Lösung, die nur 60 Mal langsamer läuft, ist unendlich besser als keine Lösung.) –

4

Hier ist meine erste Schätzung von der Spitze meines Kopfes (möglicherweise nicht die beste Lösung).

Die Curry-Funktion könnte etwas Speicher aus dem Heap reservieren und die Parameterwerte in diesen Heap-allozierten Speicher legen. Der Trick besteht dann darin, dass die zurückgegebene Funktion weiß, dass sie ihre Parameter aus diesem Heap-allokierten Speicher lesen soll. Wenn es nur eine Instanz der zurückgegebenen Funktion gibt, kann ein Zeiger auf diese Parameter in einem Singleton/global gespeichert werden. Andernfalls, wenn es mehr als eine Instanz der zurück Funktion ist, dann denke ich, dass Curry jede Instanz der zurückgegebenen Funktion in dem Heap zugewiesenen Speicher zu schaffen, muss (durch OP-Codes wie „erhalten diesen Zeiger auf die Parameter“, „Push-Schreiben die Parameter ", und" rufen diese andere Funktion in den Heap-allokierten Speicher auf. In diesem Fall müssen Sie sich in Acht nehmen, ob der zugewiesene Speicher ausführbar ist und vielleicht (ich weiß nicht) sogar Angst vor Antiviren-Programmen haben.

6

GCC bietet eine Erweiterung für die Definition verschachtelter Funktionen. Obwohl dies nicht ISO-Standard C ist, kann dies von Interesse sein, da es die Frage recht bequem beantworten kann. Kurz gesagt, die geschachtelte Funktion kann auf die lokalen Variablen der Elternfunktion zugreifen, und Zeiger auf sie können ebenfalls von der Elternfunktion zurückgegeben werden.

Hier ist ein kurzes, selbsterklärende Beispiel:

#include <stdio.h> 

typedef int (*two_var_func) (int, int); 
typedef int (*one_var_func) (int); 

int add_int (int a, int b) { 
    return a+b; 
} 

one_var_func partial (two_var_func f, int a) { 
    int g (int b) { 
     return f (a, b); 
    } 
    return g; 
} 

int main (void) { 
    int a = 1; 
    int b = 2; 
    printf ("%d\n", add_int (a, b)); 
    printf ("%d\n", partial (add_int, a) (b)); 
} 

Es gibt jedoch eine Beschränkung diese Konstruktion. Wenn Sie einen Zeiger auf die sich ergebende Funktion zu halten, wie in

one_var_func u = partial (add_int, a); 

der Funktionsaufruf u(0) in einem unerwarteten Verhalten führen kann, als die Variable a die u liest zerstört wurde kurz nach partial beendet.

Siehe this section of GCC's documentation.

+5

Aus dem Handbuch (unter dem von Ihnen angegebenen Link): "Wenn Sie versuchen, die verschachtelte Funktion über ihre Adresse aufzurufen, nachdem die enthaltende Funktion beendet wurde, wird die Hölle los!" –

+0

Wenn Sie sich bereits auf GCC beschränken, können Sie Anweisungsausdrücke verwenden, um die Hölle zu verschieben, bis die aufrufende Funktion beendet wird (d. H. Funktioniert nur für asynchrone Rückrufe): https://gist.github.com/a3f/2729c1248d0f2ee39b4a – a3f

0

Hier ist ein Ansatz zum Currieren in C.Während diese Beispielanwendung die C++ - Iostream-Ausgabe für Bequemlichkeit verwendet, ist es alle C-Stil-Codierung. Der Schlüssel zu diesem Ansatz ist eine struct, die ein Array von unsigned char enthält und dieses Array wird verwendet, um eine Argumentliste für eine Funktion aufzubauen. Die aufzurufende Funktion wird als eines der Argumente angegeben, die in das Array geschoben werden. Das resultierende Array wird dann einer Proxy-Funktion übergeben, die das Schließen von Funktion und Argumenten ausführt.

In diesem Beispiel stelle ich einige typspezifische Hilfsfunktionen zur Verfügung, um Argumente in die Schließung zu drücken, sowie eine generische pushMem() Funktion, um eine struct oder andere Speicherregion zu schieben.

Dieser Ansatz erfordert die Zuweisung eines Speicherbereichs, der dann für die Abschlussdaten verwendet wird. Es wäre am besten, den Stapel für diesen Speicherbereich zu verwenden, so dass die Speicherverwaltung kein Problem wird. Es gibt auch die Frage, wie groß der Speicherbereich für die Schließung sein soll, so daß genügend Platz für die notwendigen Argumente vorhanden ist, aber nicht so groß, daß überschüssiger Speicherplatz im Speicher oder auf dem Stapel von ungenutztem Speicherplatz belegt wird.

Ich habe mit der Verwendung einer etwas anders definierten Closure-Struktur experimentiert, die ein zusätzliches Feld für die aktuell verwendete Größe des Arrays enthält, das zum Speichern der Verschlussdaten verwendet wird. Diese andere Closure-Struktur wird dann mit einer modifizierten Hilfsfunktion verwendet, die die Notwendigkeit für den Benutzer der Hilfsfunktionen beseitigt, seinen eigenen Zeiger unsigned char * beizubehalten, wenn er Argumente zu der Closure-Struktur hinzufügt.

Hinweise und Einsprüche

Das folgende Beispielprogramm wurden mit Visual Studio 2013. Die Ausgabe dieser Probe zur Verfügung gestellt wird unten zusammengestellt und geprüft. Ich bin nicht sicher über die Verwendung von GCC oder CLANG mit diesem Beispiel, noch bin ich sicher, dass Probleme mit einem 64-Bit-Compiler gesehen werden, da ich den Eindruck habe, dass mein Test mit einer 32-Bit-Anwendung war. Auch dieser Ansatz würde nur mit Funktionen funktionieren, die die C-Standarddeklaration verwenden, in der die aufrufende Funktion das Abrufen der Argumente vom Stapel verarbeitet, nachdem der Aufrufer zurückgegeben wurde (__cdecl und nicht __stdcall in Windows-API).

Da wir die Argumentliste zur Laufzeit erstellen und dann eine Proxyfunktion aufrufen, erlaubt dieser Ansatz dem Compiler keine Überprüfung von Argumenten. Dies könnte zu mysteriösen Fehlern aufgrund nicht übereinstimmender Parametertypen führen, die der Compiler nicht kennzeichnen kann.

Anwendungsbeispiel

// currytest.cpp : Defines the entry point for the console application. 
// 
// while this is C++ usng the standard C++ I/O it is written in 
// a C style so as to demonstrate use of currying with C. 
// 
// this example shows implementing a closure with C function pointers 
// along with arguments of various kinds. the closure is then used 
// to provide a saved state which is used with other functions. 

#include "stdafx.h" 
#include <iostream> 

// notation is used in the following defines 
// - tname is used to represent type name for a type 
// - cname is used to represent the closure type name that was defined 
// - fname is used to represent the function name 

#define CLOSURE_MEM(tname,size) \ 
    typedef struct { \ 
     union { \ 
      void *p; \ 
      unsigned char args[size + sizeof(void *)]; \ 
     }; \ 
    } tname; 

#define CLOSURE_ARGS(x,cname) *(cname *)(((x).args) + sizeof(void *)) 
#define CLOSURE_FTYPE(tname,m) ((tname((*)(...)))(m).p) 

// define a call function that calls specified function, fname, 
// that returns a value of type tname using the specified closure 
// type of cname. 
#define CLOSURE_FUNC(fname, tname, cname) \ 
    tname fname (cname m) \ 
    { \ 
     return ((tname((*)(...)))m.p)(CLOSURE_ARGS(m,cname)); \ 
    } 

// helper functions that are used to build the closure. 
unsigned char * pushPtr(unsigned char *pDest, void *ptr) { 
    *(void * *)pDest = ptr; 
    return pDest + sizeof(void *); 
} 

unsigned char * pushInt(unsigned char *pDest, int i) { 
    *(int *)pDest = i; 
    return pDest + sizeof(int); 
} 

unsigned char * pushFloat(unsigned char *pDest, float f) { 
    *(float *)pDest = f; 
    return pDest + sizeof(float); 
} 

unsigned char * pushMem(unsigned char *pDest, void *p, size_t nBytes) { 
    memcpy(pDest, p, nBytes); 
    return pDest + nBytes; 
} 


// test functions that show they are called and have arguments. 
int func1(int i, int j) { 
    std::cout << " func1 " << i << " " << j; 
    return i + 2; 
} 

int func2(int i) { 
    std::cout << " func2 " << i; 
    return i + 3; 
} 

float func3(float f) { 
    std::cout << " func3 " << f; 
    return f + 2.0; 
} 

float func4(float f) { 
    std::cout << " func4 " << f; 
    return f + 3.0; 
} 

typedef struct { 
    int i; 
    char *xc; 
} XStruct; 

int func21(XStruct m) { 
    std::cout << " fun21 " << m.i << " " << m.xc << ";"; 
    return m.i + 10; 
} 

int func22(XStruct *m) { 
    std::cout << " fun22 " << m->i << " " << m->xc << ";"; 
    return m->i + 10; 
} 

void func33(int i, int j) { 
    std::cout << " func33 " << i << " " << j; 
} 

// define my closure memory type along with the function(s) using it. 

CLOSURE_MEM(XClosure2, 256)   // closure memory 
CLOSURE_FUNC(doit, int, XClosure2) // closure execution for return int 
CLOSURE_FUNC(doitf, float, XClosure2) // closure execution for return float 
CLOSURE_FUNC(doitv, void, XClosure2) // closure execution for void 

// a function that accepts a closure, adds additional arguments and 
// then calls the function that is saved as part of the closure. 
int doitargs(XClosure2 *m, unsigned char *x, int a1, int a2) { 
    x = pushInt(x, a1); 
    x = pushInt(x, a2); 
    return CLOSURE_FTYPE(int, *m)(CLOSURE_ARGS(*m, XClosure2)); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int k = func2(func1(3, 23)); 
    std::cout << " main (" << __LINE__ << ") " << k << std::endl; 

    XClosure2 myClosure; 
    unsigned char *x; 

    x = myClosure.args; 
    x = pushPtr(x, func1); 
    x = pushInt(x, 4); 
    x = pushInt(x, 20); 
    k = func2(doit(myClosure)); 
    std::cout << " main (" << __LINE__ << ") " << k << std::endl; 

    x = myClosure.args; 
    x = pushPtr(x, func1); 
    x = pushInt(x, 4); 
    pushInt(x, 24);    // call with second arg 24 
    k = func2(doit(myClosure)); // first call with closure 
    std::cout << " main (" << __LINE__ << ") " << k << std::endl; 
    pushInt(x, 14);    // call with second arg now 14 not 24 
    k = func2(doit(myClosure)); // second call with closure, different value 
    std::cout << " main (" << __LINE__ << ") " << k << std::endl; 

    k = func2(doitargs(&myClosure, x, 16, 0)); // second call with closure, different value 
    std::cout << " main (" << __LINE__ << ") " << k << std::endl; 

    // further explorations of other argument types 

    XStruct xs; 

    xs.i = 8; 
    xs.xc = "take 1"; 
    x = myClosure.args; 
    x = pushPtr(x, func21); 
    x = pushMem(x, &xs, sizeof(xs)); 
    k = func2(doit(myClosure)); 
    std::cout << " main (" << __LINE__ << ") " << k << std::endl; 

    xs.i = 11; 
    xs.xc = "take 2"; 
    x = myClosure.args; 
    x = pushPtr(x, func22); 
    x = pushPtr(x, &xs); 
    k = func2(doit(myClosure)); 
    std::cout << " main (" << __LINE__ << ") " << k << std::endl; 

    x = myClosure.args; 
    x = pushPtr(x, func3); 
    x = pushFloat(x, 4.0); 

    float dof = func4(doitf(myClosure)); 
    std::cout << " main (" << __LINE__ << ") " << dof << std::endl; 

    x = myClosure.args; 
    x = pushPtr(x, func33); 
    x = pushInt(x, 6); 
    x = pushInt(x, 26); 
    doitv(myClosure); 
    std::cout << " main (" << __LINE__ << ") " << std::endl; 

    return 0; 
} 

Testausgang

Ausgabe aus diesem Beispielprogramm. Die Zahl in Klammern ist die Zeilennummer im Hauptverzeichnis, an der der Funktionsaufruf erfolgt.

func1 3 23 func2 5 main (118) 8 
func1 4 20 func2 6 main (128) 9 
func1 4 24 func2 6 main (135) 9 
func1 4 14 func2 6 main (138) 9 
func1 4 16 func2 6 main (141) 9 
fun21 8 take 1; func2 18 main (153) 21 
fun22 11 take 2; func2 21 main (161) 24 
func3 4 func4 6 main (168) 9 
func33 6 26 main (175) 
Verwandte Themen