Functional Programming (Currying) in C / Issue with Types
作为染上羊毛的函数式程序员,我发现很难不尝试将自己喜欢的范例塞进我正在使用的任何语言中。在编写C时,我发现我想使用我的函数之一,然后传递部分应用的函数。阅读后,有什么方法可以在C语言中进行计算?并注意我提出的http://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html#Nested-Functions中的警告:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <stdio.h> 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 ",f1(g(2))); } |
哪个按预期运行。但是,我的原始程序可用于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <stdio.h> 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 ",f1(g(2))); } |
这将产生如下内容:
1 2 3 4 | bash-3.2$ ./a.out Segmentation fault: 11 bash-3.2$ ./a.out Illegal instruction: 4 |
错误的选择似乎是随机的。此外,如果使用
我的gcc是
编辑:明确地说,我了解我要做的是愚蠢的。此代码不会在"好奇号"流动站或NYSE上运行。我试图更多地了解(GNU)C中的函数指针如何工作,并解释一些有趣的发现。我保证在现实世界中永远不会做这样的事情。
一个有趣的问题,我看了引用的答案中的论文(具有Curried函数的C / C ++ / Objective-C具有更高的函数可重用性)。
好。
因此,以下是您可能要去的建议路径。我认为这不是真正的Curried函数,因为我不完全理解本文所讲的内容,而不是作为函数式程序员。但是,通过做一些工作,我发现其中一些有趣的应用。另一方面,我不确定这是否是您想要的,这让我震惊,您可以在C中完成此操作。
好。
似乎有两个问题。
好。
首先是能够处理带有任意参数列表的任意函数调用。我采用的方法是使用标准C库变量参数功能(带有va_start(),va_arg()和va_end()函数的va_list),然后将函数指针与提供的参数一起存储到数据区域中,以便它们然后可以在以后的时间执行。我借用并修改了
好。
接下来是函数及其参数列表的存储。我只是使用一个任意大小的结构来尝试这个概念。这将需要更多的思考。
好。
此特定版本使用的数组被视为堆栈。有一个函数,您可以使用它的参数将任意函数推入堆栈数组,还有一个函数会将最上面的函数及其参数从堆栈数组中弹出并执行。
好。
但是,实际上您可以在某种集合中具有任意的struct对象,例如哈希映射,这可能非常酷。
好。
我只是从论文中借用了信号处理程序示例,以表明该概念适用于此类应用程序。
好。
所以这是源代码,希望它能帮助您提出解决方案。
好。
您将需要向开关添加其他情况,以便能够处理其他参数类型。我只是做了一些概念验证。
好。
尽管表面上看起来是相当简单的扩展,但这也不执行调用函数的函数。就像我说的,我并没有完全理解Curried。
好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | #include <stdarg.h> #include <string.h> // 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 ", i, p); } void otherFunc (int i, char * p, char *p2) { printf (" i = %d, char = %s, char =%s ", 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; } |
您可以从中获得的另一点乐趣是,在
好。
例如,如果您在上面的源代码中将函数
好。
1 2 3 4 5 6 | void otherFunc (int i, char * p, char *p2) { printf (" i = %d, char = %s, char =%s ", i, p, p2); pushFunction ((void (*)(...))myFunc,"is", i+2, p); } |
如果您随后向
好。
编辑2:
如果我们添加以下函数,它将执行所有已放入堆栈的函数。通过将函数和参数推入堆栈,然后执行一系列函数调用,这将使我们能够从根本上创建一个小程序。此函数还将允许我们推送没有任何参数的函数,然后推送一些参数。当将函数弹出堆栈时,如果参数块没有有效的函数指针,那么我们要做的就是将该参数列表放到堆栈顶部的参数块上,然后执行该函数。也可以对上面的函数
好。
实际上,这可能是线程翻译的基础。
好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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(); } } |
编辑3:
然后,我们对
好。
1 2 3 4 5 6 7 8 9 |
因此,使用此新功能,我们可以执行以下操作。首先创建类似于原始问题的两个函数。我们将在一个函数中使用pushFunction()来推送参数,然后该参数将由堆栈上的下一个函数使用。
好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
新增功能我们将新功能与以下一系列语句结合使用:
好。
1 2 3 4 | double xDouble = 4.5; pushFunction ((void (*)(...))f1, 0); pushFunction ((void (*)(...))g2,"d", xDouble); executeFuncStack(); |
这些语句将首先执行值为4.5的函数
好。
好。
您试图依赖未定义的行为:内部函数超出范围,因为外部函数确实退出了,通过某个指针调用该内部函数的行为是不确定的。可能发生任何事情。对于整数情况意外地起作用的事实并不意味着您可以期望对double使用相同的东西,甚至对于不同的编译器,不同的编译器版本,不同的编译器标志或不同的目标体系结构,对于int都可以期望相同。
因此,不要依赖不确定的行为。实际上,您针对这些警告采取了行动时,请不要声称自己已"听取了这些警告"。该警告明确指出:
If you try to call the nested function through its address after the containing function has exited, all hell will break loose.
C中没有闭包,因此从这种意义上讲,不会有任何麻烦。如果将一些数据传递给函数调用,则可以得到类似的效果,但是它看起来不像正常的函数调用,因此不会像普通的柯里化那样。 C ++在那里具有更大的灵活性,因为它允许对象在语法上类似于函数。在C ++世界中,通常将currying称为函数参数的"绑定"。
如果您真的想知道为什么一段代码可以工作,而另一段代码却失败,则可以采用汇编代码(例如由
上面代码的固定版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h> 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, e.g. static double local_b = g(0, 1); */ return wrapfunc(a, 1); } int main () { printf ("(g(2))(1)=%f ", f1(g,2)); } |
有关示例参数示例的其他操作功能的更多信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include<iostream> #include<cstdio> using namespace std; #define N 4 #define LOOP(i) for(i=0; i<N; i++) #define F(i) ( (int(*)(int,int))opt[i] ) #define FI F(i) #define FJ F(j) #define FK F(k) int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } |
u int mul(int a,int b){返回a * b; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | 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 " 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 " return 0; } howObtain24(num, opt); return 0; } |
请原谅我没有得到它,但是为什么不包装而不是咖喱呢,因为无论如何您都是在编译时声明函数的? currying的优点是-或至少在我看来-您可以在运行时定义部分应用的函数,但是在这里您无需这样做。还是我错过了什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <stdio.h> // 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, e.g. static double local_b = g(0, 1); */ return g(a, 1); } int main () { printf ("(g(2))(1)=%f ", f1(2)); } |