Programmation fonctionnelle (currying) en C / Issue avec types

En tant que programmeur fonctionnel «teint dans la peau», j’ai du mal à ne pas essayer de cadrer mon paradigme préféré dans la langue que j’utilise. En écrivant des éléments de CI, j’ai trouvé que j’aimerais occuper l’une de mes fonctions, puis transmettre la fonction partiellement appliquée. Après avoir lu Y a-t-il un moyen de faire le curry en C? et en tenant compte des avertissements sur http://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html#Nested-Functions que j’ai trouvé:

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

Qui fonctionne comme prévu. Cependant, mon programme original fonctionnait avec double s et je me suis dit que je changerais simplement les types appropriés et que tout irait 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))); } 

Cela produit des choses comme:

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

le choix de l’erreur étant apparemment aléatoire. De plus, si l’un des exemples est compilé avec -O3 le compilateur génère lui-même une Segmentation fault: 11 . Je ne reçois aucun avertissement de gcc, et je ne suis pas en mesure de comprendre ce qui se passe. Est-ce que quelqu’un sait pourquoi le deuxième programme échoue alors que le premier ne le fait pas? Ou mieux encore, comment réparer le second?

Mon gcc est i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00) et mon kernel est la 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 .

edit: Pour être clair, je comprends que ce que j’essaie de faire est stupide. Ce code ne sera pas exécuté sur le mobile Curiosity ou le NYSE. J’essaie de mieux comprendre le fonctionnement des pointeurs de fonction dans (GNU) C et d’expliquer quelque chose d’intéressant que j’ai trouvé. Je promets de ne jamais rien faire de pareil dans le monde réel.

Une question intéressante et j’ai jeté un coup d’œil au document dans la réponse citée ( Réutilisabilité plus fonctionnelle en C / C ++ / Objective-C avec les fonctions Curried ).

Donc, voici un chemin suggéré vers l’endroit où vous voudrez peut-être aller. Je ne pense pas que ce soit vraiment une fonction curry car je ne comprends pas bien de quoi parle le journal, pas un programmeur fonctionnel. Cependant, en faisant un peu de travail, je trouve une application intéressante de certains de ce concept. Je ne suis pas sûr que ce soit ce que vous voulez en revanche, cela me coupe l’esprit que vous pouvez le faire en C.

Il semblait y avoir deux problèmes.

Tout d’abord, être capable de gérer des appels de fonction arbitraires avec des listes d’arguments arbitraires. L’approche que j’ai adoptée consistait à utiliser la fonctionnalité standard des arguments variables de la bibliothèque C (va_list avec les fonctions va_start (), va_arg () et va_end ()), puis à stocker le pointeur de la fonction avec les arguments fournis dans une zone de données pourrait alors être exécuté plus tard. J’ai emprunté et modifié la manière dont la fonction printf() utilise la ligne de format pour savoir combien d’arguments et leurs types sont fournis.

La prochaine était le stockage de la fonction et sa liste d’arguments. Je viens d’utiliser une structure de taille arbitraire pour essayer le concept. Cela nécessitera un peu plus de reflection.

Cette version particulière utilise un tableau traité comme une stack. Il existe une fonction que vous utilisez pour insérer une fonction quelconque avec ses arguments dans le tableau de stack et une fonction qui extraira la fonction la plus en haut et ses arguments du tableau de stack et l’exécutera.

Cependant, vous pourriez simplement avoir des objects struct arbitraires dans une sorte de collection, par exemple une carte de hachage, ce qui pourrait être très cool.

Je viens d’emprunter l’exemple du gestionnaire de signaux au journal pour montrer que le concept fonctionnerait avec ce type d’application.

Voici donc le code source et j’espère que cela vous aidera à trouver une solution.

Vous devez append d’autres cas au commutateur pour pouvoir traiter d’autres types d’arguments. Je viens d’en faire quelques-uns pour la validation technique.

De plus, la fonction appelant une fonction ne fonctionne pas, bien que cela semble à première vue être une extension assez simple. Comme je l’ai dit, je ne comprends pas totalement ce truc au 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': // ssortingng 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 = "ssortingng"; char *p2 = "ssortingng 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; } 

Vous pouvez également vous amuser avec la fonction pushFunction() dans une fonction appelée par doFuncAndPop() pour doFuncAndPop() une autre fonction que vous pouvez mettre sur la stack avec ses arguments.

Par exemple, si vous modifiez la fonction otherFunc() dans la source ci-dessus pour qu’elle ressemble à ceci:

 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 vous ajoutez ensuite un autre appel à doFuncAndPop() vous verrez que le premier otherFunc() est exécuté, puis l’appel à myFunc() utilisé dans otherFunc() est exécuté, puis l’appel de myFunc() placé dans le otherFunc() main () s’appelle.

EDIT 2: Si nous ajoutons la fonction suivante, toutes les fonctions placées dans la stack seront exécutées. Cela nous permettra de créer un petit programme en plaçant des fonctions et des arguments sur notre stack, puis en exécutant la série d’appels de fonctions. Cette fonction nous permettra également de pousser une fonction sans aucun argument, puis de pousser certains arguments. Lorsque nous extrayons des fonctions de notre stack, si un bloc d’arguments n’a pas de pointeur de fonction valide, nous devons alors placer cette liste d’arguments sur le bloc d’arguments situé en haut de la stack, puis l’exécuter. Une modification similaire peut également être apscope à la fonction doFuncAndPop() ci-dessus. Et si nous utilisons l’opération pushFunction () dans une fonction exécutée, nous pouvons faire certaines choses intéressantes.

En fait, cela pourrait servir de base à un interprète threadé .

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

EDIT 3: Nous modifions ensuite pushFunc() pour traiter un double avec le commutateur supplémentaire suivant:

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

Donc, avec cette nouvelle fonction, nous pouvons faire quelque chose comme ceci. Tout d’abord, créez nos deux fonctions similaires à la question initiale. Nous allons utiliser la fonction pushFunction () dans une fonction pour pousser les arguments qui sont ensuite utilisés par la fonction suivante de 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; } 

Nouveau, nous utilisons notre nouvelle fonctionnalité avec les séries d’énoncés suivantes:

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

Ces instructions exécuteront d’abord la fonction g2() avec la valeur 4.5, puis la fonction g2() poussera sa valeur de retour sur notre stack pour être utilisée par la fonction f1() qui a été empilée en premier sur notre stack.

Vous essayez de vous fier à un comportement non défini: une fois que la fonction interne est hors de scope parce que la fonction externe se ferme, le comportement consistant à appeler cette fonction interne via un pointeur est indéfini. Tout peut arriver. Le fait que les choses aient fonctionné accidentellement pour le cas entier ne signifie pas que vous pouvez vous attendre à la même chose pour double, ni même pour différents compilateurs, versions de compilateur différentes, indicateurs de compilateur différents ou architectures cibles différentes.

Alors ne comptez pas sur un comportement indéfini. Ne prétendez pas avoir «écouté les avertissements», alors qu’en réalité vous avez agi contre ces avertissements. L’avertissement indique clairement:

Si vous essayez d’appeler la fonction nestede par son adresse après la sortie de la fonction contenant, tout l’enfer se déchaînera.

Il n’y a pas de fermeture en C, il ne peut donc pas y avoir de curry dans ce sens. Vous pouvez obtenir des effets similaires si vous transmettez certaines données à des invocations de fonctions, mais cela ne ressemblera pas exactement à un appel de fonction normal. Vous ne vous sentirez donc pas comme un curry normal. C ++ offre plus de flexibilité dans la mesure où il permet aux objects de se comporter de manière syntaxique comme des fonctions. Dans le monde C ++, le curry est généralement appelé ” liaison ” des parameters de fonction.

Si vous voulez vraiment savoir pourquoi un morceau de code a fonctionné alors que l’autre a échoué, vous pouvez utiliser le code d’assemblage (généré par exemple par gcc -S -fverbose-asm ) et simuler l’exécution dans votre tête pour voir ce qu’il advient de vos données. et d’autres choses. Ou vous pouvez utiliser le débogueur pour voir où les choses échouent ou si les emplacements de données changent. Cela pourrait prendre du travail et je doute que cela en vaille la peine.

Excusez-moi de ne pas l’avoir, mais pourquoi ne pas envelopper au lieu de curry , puisque vous déclarez des fonctions au moment de la compilation de toute façon? L’avantage du currying est – ou du moins, me semble-t-il – que vous pouvez définir une fonction partiellement appliquée au moment de l’exécution, mais ici vous ne le faites pas. Ou est-ce que je manque quelque chose?

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

Version fixe du code ci-dessus

 #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 peu plus sur différentes fonctions d’opération avec l’exemple de paramètre exemple

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

u int mul (int a, int b) {retour 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; }