Progtwigción Funcional (Currying) en C / Problema con Tipos

Como progtwigdor funcional teñido en la lana, me resulta difícil no tratar de modificar mi paradigma favorito en cualquier idioma que esté usando. Mientras escribía algo de CI, me gustaría curry una de mis funciones y luego pasar la función parcialmente aplicada. Después de leer ¿Hay una manera de hacer curry en C? y haciendo caso a las advertencias en http://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html#Nested-Functions se me ocurrió:

#include  typedef int (*function) (int); function g (int a) { int f (int b) { return a+b; } return f; } int f1(function f){ return f(1);} int main () { printf ("(g(2))(1)=%d\n",f1(g(2))); } 

Que se ejecuta como se esperaba. Sin embargo, mi progtwig original funciona con double s, por lo que pensé que solo cambiaría los tipos adecuados y estaría bien:

 #include  typedef double (*function) (double); function g (double a) { double f (double b) { return a+b; } return f; } double f1(function f){ return f(1);} int main () { printf ("(g(2))(1)=%e\n",f1(g(2))); } 

Esto produce cosas como:

 bash-3.2$ ./a.out Segmentation fault: 11 bash-3.2$ ./a.out Illegal instruction: 4 

con la elección del error aparentemente al azar. Además, si cualquiera de los ejemplos se comstack con -O3 el comstackdor en sí produce un Segmentation fault: 11 sí. No recibo advertencias de gcc en ningún momento, y no puedo averiguar qué está sucediendo. ¿Alguien sabe por qué el segundo progtwig falla mientras que el primero no? O mejor aún, ¿cómo arreglar el segundo?

Mi gcc es i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00) y mi núcleo es Darwin Kernel Version 12.1.0: Tue Aug 14 13:29:55 PDT 2012; root:xnu-2050.9.2~1/RELEASE_X86_64 Darwin Kernel Version 12.1.0: Tue Aug 14 13:29:55 PDT 2012; root:xnu-2050.9.2~1/RELEASE_X86_64 .

Edición: para ser claros, entiendo que lo que estoy tratando de hacer es estúpido. Este código no se ejecutará en el rover Curiosity o en el NYSE. Estoy tratando de entender más sobre cómo funcionan los punteros de función en (GNU) C, y explicar algo interesante que encontré. Prometo nunca hacer algo así en el mundo real.

Una pregunta interesante y eché un vistazo al artículo en la respuesta citada ( Reutilización más funcional en C / C ++ / Objective-C con funciones Curried ).

Así que, a continuación, le sugerimos una ruta a donde le gustaría ir. No creo que esta sea realmente una función de Curried ya que no entiendo completamente de qué se trata el documento, no soy un progtwigdor funcional. Sin embargo, haciendo un poco de trabajo, encuentro una aplicación interesante de algunos de este concepto. No estoy seguro de que esto es lo que quieres, por otra parte, me sorprende que puedas hacer esto en C.

Parecía que había dos problemas.

Lo primero de todo era poder manejar llamadas de función arbitrarias con listas de argumentos arbitrarios. El enfoque que adopté fue utilizar la funcionalidad estándar de los argumentos de la variable de la Biblioteca C (va_list con las funciones va_start (), va_arg () y va_end ()) y luego almacenar el puntero de la función junto con los argumentos proporcionados en un área de datos para que Entonces podría ser ejecutado en un momento posterior. Tomé prestado y modifiqué cómo la printf() usa la línea de formato para saber cuántos argumentos y sus tipos se proporcionan.

El siguiente fue el almacenamiento de la función y su lista de argumentos. Acabo de usar una estructura con un tamaño arbitrario para probar el concepto. Esto necesitará un poco más de reflexión.

Esta versión en particular utiliza una matriz que se trata como una stack. Hay una función que utiliza para empujar alguna función arbitraria con sus argumentos en la matriz de stack y hay una función que sacará la función más superior y sus argumentos fuera de la matriz de stack y la ejecutará.

Sin embargo, en realidad podría tener objetos de estructura arbitrarios en algún tipo de colección, por ejemplo, un mapa hash y eso podría ser muy bueno.

Simplemente tomé prestado el ejemplo del controlador de señales del documento para mostrar que el concepto funcionaría con ese tipo de aplicación.

Así que aquí está el código fuente y espero que le ayude a encontrar una solución.

Deberá agregar otros casos al conmutador para poder procesar otros tipos de argumentos. Acabo de hacer algunos como prueba de concepto.

Además, esto no hace que la función llame a una función, aunque parece que en la superficie es una extensión bastante sencilla. Como dije, no entiendo totalmente esta cosa al curry.

 #include  #include  // a struct which describes the function and its argument list. typedef struct { void (*f1)(...); // we have to have a struct here because when we call the function, // we will just pass the struct so that the argument list gets pushed // on the stack. struct { unsigned char myArgListArray[48]; // area for the argument list. this is just an arbitray size. } myArgList; } AnArgListEntry; // these are used for simulating a stack. when functions are processed // we will just push them onto the stack and later on we will pop them // off so as to run them. static unsigned int myFunctionStackIndex = 0; static AnArgListEntry myFunctionStack[1000]; // this function pushes a function and its arguments onto the stack. void pushFunction (void (*f1)(...), char *pcDescrip, ...) { char *pStart = pcDescrip; AnArgListEntry MyArgList; unsigned char *pmyArgList; va_list argp; int i; char c; char *s; void *p; va_start(argp, pcDescrip); pmyArgList = (unsigned char *)&MyArgList.myArgList; MyArgList.f1 = f1; for ( ; *pStart; pStart++) { switch (*pStart) { case 'i': // integer argument i = va_arg(argp, int); memcpy (pmyArgList, &i, sizeof(int)); pmyArgList += sizeof(int); break; case 'c': // character argument c = va_arg(argp, char); memcpy (pmyArgList, &c, sizeof(char)); pmyArgList += sizeof(char); break; case 's': // string argument s = va_arg(argp, char *); memcpy (pmyArgList, &s, sizeof(char *)); pmyArgList += sizeof(char *); break; case 'p': // void pointer (any arbitray pointer) argument p = va_arg(argp, void *); memcpy (pmyArgList, &p, sizeof(void *)); pmyArgList += sizeof(void *); break; default: break; } } va_end(argp); myFunctionStack[myFunctionStackIndex] = MyArgList; myFunctionStackIndex++; } // this function will pop the function and its argument list off the top // of the stack and execute it. void doFuncAndPop () { if (myFunctionStackIndex > 0) { myFunctionStackIndex--; myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList); } } // the following are just a couple of arbitray test functions. // these can be used to test that the functionality works. void myFunc (int i, char * p) { printf (" i = %d, char = %s\n", i, p); } void otherFunc (int i, char * p, char *p2) { printf (" i = %d, char = %s, char =%s\n", i, p, p2); } void mySignal (int sig, void (*f)(void)) { f(); } int main(int argc, char * argv[]) { int i = 3; char *p = "string"; char *p2 = "string 2"; // push two different functions on to our stack to save them // for execution later. pushFunction ((void (*)(...))myFunc, "is", i, p); pushFunction ((void (*)(...))otherFunc, "iss", i, p, p2); // pop the function that is on the top of the stack and execute it. doFuncAndPop(); // call a function that wants a function so that it will execute // the current function with its argument lists that is on top of the stack. mySignal (1, doFuncAndPop); return 0; } 

Un poco más de diversión que puede tener con esto es usar la función pushFunction() dentro de una función que invoca doFuncAndPop() para tener otra función que pueda poner en la stack con sus argumentos.

Por ejemplo, si modifica la función otherFunc() en la fuente anterior para parecerse a lo siguiente:

 void otherFunc (int i, char * p, char *p2) { printf (" i = %d, char = %s, char =%s\n", i, p, p2); pushFunction ((void (*)(...))myFunc, "is", i+2, p); } 

si luego agrega otra llamada a doFuncAndPop() verá que primero se otherFunc() luego se ejecuta la llamada a myFunc() que se puse en otherFunc() y finalmente se myFunc() llamada myFunc() que fue empujada en el main () se llama.

EDIT 2: si agregamos la siguiente función, esto ejecutará todas las funciones que se han colocado en la stack. Básicamente, esto nos permitirá crear un pequeño progtwig presionando funciones y argumentos en nuestra stack y luego ejecutando la serie de llamadas a funciones. Esta función también nos permitirá empujar una función sin ningún argumento y luego empujar algunos argumentos. Cuando se eliminan las funciones de nuestra stack, si un bloque de argumentos no tiene un puntero de función válido, lo que hacemos es colocar esa lista de argumentos en el bloque de argumentos en la parte superior de la stack y luego ejecutarla. También se puede hacer un cambio similar a la función doFuncAndPop() anterior. Y si usamos la operación pushFunction () en una función ejecutada, podemos hacer algunas cosas interesantes.

En realidad, esto podría ser la base para un intérprete de hilos .

 // execute all of the functions that have been pushed onto the stack. void executeFuncStack () { if (myFunctionStackIndex > 0) { myFunctionStackIndex--; // if this item on the stack has a function pointer then execute it if (myFunctionStack[myFunctionStackIndex].f1) { myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList); } else if (myFunctionStackIndex > 0) { // if there is not a function pointer then assume that this is an argument list // for a function that has been pushed on the stack so lets execute the previous // pushed function with this argument list. int myPrevIndex = myFunctionStackIndex - 1; myFunctionStack[myPrevIndex].myArgList = myFunctionStack[myFunctionStackIndex].myArgList; } executeFuncStack(); } } 

EDITAR 3: Luego hacemos un cambio a pushFunc() para manejar un doble con el siguiente interruptor adicional:

 case 'd': { double d; // double argument d = va_arg(argp, double); memcpy (pmyArgList, &d, sizeof(double)); pmyArgList += sizeof(double); } break; 

Así que con esta nueva función podemos hacer algo como lo siguiente. En primer lugar, cree nuestras dos funciones similares a la pregunta original. Usaremos pushFunction () dentro de una función para empujar argumentos que luego son usados ​​por la siguiente función en la stack.

 double f1 (double myDouble) { printf ("f1 myDouble = %f\n", myDouble); return 0.0; } double g2 (double myDouble) { printf ("g2 myDouble = %f\n", myDouble); myDouble += 10.0; pushFunction (0, "d", myDouble); return myDouble; } 

Nuevo usamos nuestra nueva funcionalidad con la siguiente serie de declaraciones:

 double xDouble = 4.5; pushFunction ((void (*)(...))f1, 0); pushFunction ((void (*)(...))g2, "d", xDouble); executeFuncStack(); 

Estas instrucciones ejecutarán primero la función g2() con el valor de 4.5 y luego la función g2() empujará su valor de retorno a nuestra stack para que la función f1() use en nuestra stack.

Está intentando confiar en un comportamiento indefinido: una vez que la función interna se sale de su scope porque la externa sale, el comportamiento de llamar a esa función interna a través de algún puntero no está definido. Cualquier cosa podría pasar. El hecho de que las cosas trabajaron accidentalmente para el caso de enteros no implica que pueda esperar lo mismo para el doble, o que incluso puede esperar lo mismo para int sobre comstackdores diferentes, versiones de comstackdor diferentes, indicadores de comstackdor diferentes o architectures de destino diferentes.

Entonces no confíes en un comportamiento indefinido. No diga que ha “prestado atención a las advertencias” cuando, de hecho, actuó en contra de esas advertencias. La advertencia dice claramente:

Si intenta llamar a la función anidada a través de su dirección después de que la función contenedora haya salido, todo el infierno se desatará.

No hay cierres en C, por lo que no puede haber curry en este sentido. Puede obtener efectos similares si pasa algunos datos a las invocaciones de funciones, pero eso no se verá exactamente como una llamada de función normal, por lo que no se sentirá como un curry normal. C ++ tiene más flexibilidad allí, ya que permite que los objetos se comporten sintácticamente como funciones. En el mundo de C ++, el curry suele denominarse ” enlace ” de los parámetros de la función.

Si realmente quiere saber por qué funcionó un fragmento de código pero el otro falló, puede tomar el código de ensamblaje (generado, por ejemplo, por gcc -S -fverbose-asm ) y simular la ejecución en su cabeza, para ver qué sucede con sus datos. y esas cosas O puede usar el depurador para ver dónde fallan las cosas o cómo cambian las ubicaciones de los datos. Podría tomar algo de trabajo, y dudo que valga la pena el tiempo.

Disculpe por no haberlo recibido, pero ¿por qué no envuelve en lugar de curry , ya que de todos modos está declarando funciones en tiempo de comstackción? La ventaja del curry es, o al menos, me parece a mí, que puede definir una función parcialmente aplicada en tiempo de ejecución, pero aquí no está haciendo esto. ¿O me estoy perdiendo algo?

 #include  // Basic function summing its arguments double g (double a, double b) { return a+b; } double f1(double a) { /* "1" can be replaced by a static initialized by another function, eg static double local_b = g(0, 1); */ return g(a, 1); } int main () { printf ("(g(2))(1)=%f\n", f1(2)); } 

Versión fija del código anterior

 #include  typedef double (*function) (double,double); // Basic function summing its arguments double g (double a, double b) { return a+b; } double f1(function wrapfunc,double a) { /* "1" can be replaced by a static initialized by another function, eg static double local_b = g(0, 1); */ return wrapfunc(a, 1); } int main () { printf ("(g(2))(1)=%f\n", f1(g,2)); } 

un poco más sobre diferentes funciones de operación con ejemplo de parámetro de muestra

 #include #include using namespace std; #define N 4 #define LOOP(i) for(i=0; i 

u int mul (int a, int b) {return a * b; }

 int div(int a, int b) { if (b == 0 || a % b) return 2401; return a / b; } char whichOpt(int index) { if (index == 0) return '+'; else if (index == 1) return '-'; else if (index == 2) return '*'; return '/'; } void howObtain24(int num[], void *opt[]) { int i, j, k, a, b, c, d; int ans=0; LOOP(i) LOOP(j) LOOP(k) LOOP(a) LOOP(b) LOOP(c) LOOP(d) { if (a == b || a == c || a == d || b == c || b == d || c == d) continue; if (FI(FJ(FK(num[a], num[b]), num[c]), num[d]) == 24) { std::cout << "((" << num[a] << whichOpt(k) << num[b] << ')' << whichOpt(j) << num[c] << ')' << whichOpt(i) << num[d] << endl; ans++; continue; } if (FI(FJ(num[a], num[b]), FK(num[c], num[d])) == 24) { std::cout << '(' << num[a] << whichOpt(j) << num[b] << ')' << whichOpt(i) << '(' << num[c] << whichOpt(k) << num[d] << ')' << endl; ans++; continue; } } if(ans==0) std::cout << "Non-Answer" << std::endl; return; } //======================================================================= int main() { int num[N]; void *opt[N] = { (void *)add, (void *)sub, (void *)mul, (void *)div }; std::cout << "Input 4 Numbers between 1 and 10\n" for (int i = 0; i < N; i++) cin >> num[i]; for (int j = 0; j < N; j++) if (num[j] < 1 || num[j] > 10) { std::cout << "InCorrect Input\n" return 0; } howObtain24(num, opt); return 0; }