Czy jest sposób na currying w C?

Powiedzmy, że mam wskaźnik do funkcji _stack_push(stack* stk, void* el). Chcę móc wywołać curry(_stack_push, my_stack) i odzyskać funkcję, która zajmuje void* el. Nie mogłem wymyślić, jak to zrobić, ponieważ C nie pozwala na definicję funkcji runtime, ale wiem, że są tu dużo mądrzejsi ludzie niż ja:). Jakieś pomysły?

Author: Burke, 2009-06-21

4 answers

Znalazłem artykuł Laurenta Dami, który omawia currying w C / C++ / Objective-C:

Więcej funkcji Reusability w C / C++ / Objective-C Z FUNKCJAMI Curry

Ciekawe jak to jest zaimplementowane w C:

Nasza obecna implementacja wykorzystuje istniejące konstrukcje C do dodania mechanizmu currying. Było to znacznie łatwiejsze do zrobienia niż modyfikowanie kompilatora i jest wystarczające, aby udowodnić zainteresowanie curryingiem. Podejście to ma jednak dwie wady. Pierwszy, funkcja curry nie może być sprawdzana typu, dlatego wymaga starannego użycia w celu uniknięcia błędów. Po drugie, funkcja curry nie może znać rozmiaru swoich argumentów i zlicza je tak, jakby wszystkie były wielkości liczby całkowitej.

Artykuł nie zawiera implementacji curry(), ale można sobie wyobrazić, jak jest ona zaimplementowana za pomocą wskaźników funkcji i funkcji zmiennych .

 21
Author: Jared Oberhaus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-21 06:14:38

GCC zapewnia rozszerzenie definicji funkcji zagnieżdżonych. Chociaż nie jest to norma ISO C, może to być interesujące, ponieważ pozwala odpowiedzieć na pytanie całkiem wygodnie. Krótko mówiąc, zagnieżdżona funkcja może uzyskać dostęp do zmiennych lokalnych funkcji rodzica, a wskaźniki do nich mogą być również zwracane przez funkcję rodzica.

Oto krótki, oczywisty przykład:

#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));
}

Istnieje jednak ograniczenie tej konstrukcji. Jeśli zachowasz wskaźnik do funkcja wynikowa, jak w

one_var_func u = partial (add_int, a);

Wywołanie funkcji u(0) może spowodować nieoczekiwane zachowanie, ponieważ zmienna a, którą odczytuje u została zniszczona tuż po zakończeniu partial.

PatrzTa sekcja dokumentacji GCC .

 6
Author: user43513,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-12 13:57:43

Oto moje pierwsze przypuszczenie z czubka głowy (może nie być najlepszym rozwiązaniem).

Funkcja curry może przydzielić część pamięci ze sterty i umieścić wartości parametrów w tej przydzielonej stercie pamięci. Sztuczka polega na tym, aby zwracana funkcja wiedziała, że ma odczytywać jej parametry z tej przydzielonej stercie pamięci. Jeśli jest tylko jedna instancja zwracanej funkcji, wtedy wskaźnik do tych parametrów może być przechowywany w singleton / global. W przeciwnym razie, jeśli jest więcej poza jedną instancją zwracanej funkcji, myślę, że curry musi utworzyć każdą instancję zwracanej funkcji w pamięci przydzielonej stercie (pisząc opcody takie jak "get that pointer to the parameters", "push the parameters" i "invoke that other function" do pamięci przydzielonej stercie). W takim przypadku trzeba uważać, czy przydzielona pamięć jest wykonywalna, a może (nie wiem) nawet bać się programów antywirusowych.

 4
Author: ChrisW,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-21 05:32:05

Oto podejście do robienia currying w C. Podczas Gdy ta przykładowa aplikacja jest za pomocą wyjścia C++ iostream dla wygody to wszystko C stylu kodowania.

Kluczem do tego podejścia jest posiadanie struct, która zawiera tablicę unsigned char i ta tablica jest używana do budowania listy argumentów dla funkcji. Wywołana funkcja jest określona jako jeden z argumentów, które są wepchnięte do tablicy. Wynikowa tablica jest następnie przekazywana funkcji proxy, która faktycznie wykonuje zamknięcie funkcji i argumentów.

W tym przykładzie podaję kilka specyficznych dla typu funkcji pomocniczych do wypychania argumentów do zamknięcia, a także ogólną funkcję pushMem() do wypychania struct lub innego regionu pamięci.

To podejście wymaga alokacji obszaru pamięci, który jest następnie używany do danych zamknięcia. Najlepiej byłoby użyć stosu dla tego obszaru pamięci, aby zarządzanie pamięcią nie stało się problemem. Jest też kwestia wielkości zamknięcia obszar pamięci tak, że jest wystarczająco dużo miejsca na niezbędne argumenty, ale nie tak duży, że nadmiar miejsca w pamięci lub na stosie jest zajęty przez niewykorzystaną przestrzeń.

Eksperymentowałem z użyciem nieco inaczej zdefiniowanej struktury zamknięcia, która zawiera dodatkowe pole dla aktualnie używanego rozmiaru tablicy używanej do przechowywania danych zamknięcia. Ta inna struktura zamknięcia jest następnie używana ze zmodyfikowanymi funkcjami pomocniczymi, które eliminują potrzebę użytkownika pomocnika funkcje utrzymujące własny wskaźnik unsigned char * podczas dodawania argumentów do struktury zamknięcia.

Uwagi i zastrzeżenia

Poniższy przykładowy program został skompilowany i przetestowany w Visual Studio 2013. Wyjście z tej próbki przedstawiono poniżej. Nie jestem pewien co do użycia GCC lub CLANG w tym przykładzie, ani nie jestem pewien co do problemów, które mogą być postrzegane z kompilatorem 64 bitowym, ponieważ jestem pod wrażeniem, że moje testy były z aplikacją 32 bitową. Również to podejście wydaje się działać tylko z funkcjami, które używają standardowej deklaracji C, w której funkcja wywołująca obsługuje wyskakiwanie argumentów ze stosu po zwrocie callee (__cdecl, a nie __stdcall W Windows API).

Ponieważ budujemy listę argumentów w czasie wykonywania, a następnie wywołujemy funkcję proxy, takie podejście nie pozwala kompilatorowi na sprawdzenie argumentów. Może to prowadzić do tajemniczych błędów z powodu niedopasowanych typów parametrów, których kompilator nie jest w stanie za flagę.

Przykładowa aplikacja

// 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;
}

Wyjście testowe

Wyjście z tego przykładowego programu. Liczba w nawiasie jest numerem linii w głównym, w którym wykonywane jest wywołanie funkcji.

 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)
 0
Author: Richard Chambers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-22 15:35:39