C中的函数式编程(Currying)/类型问题

作为一个染成羊毛的function性程序员,我发现很难不把我最喜欢的范例变成我正在使用的语言。 在编写一些CI时,我想要讨论我的一个函数,然后传递部分应用的函数。 看完之后有没有办法在C里做cur? 并注意到http://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html#Nested-Functions上的警告我提出:

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

哪个按预期运行。 但是,我的原始程序使用double s,所以我想我只是改变了适当的类型,我会没事的:

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

这会产生如下:

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

选择错误似乎是随机的。 此外,如果使用-O3编译任一示例,则编译器本身会抛出Segmentation fault: 11本身。 在任何时候我都没有得到gcc的警告,我无法弄清楚发生了什么。 有谁知道为什么第二个程序失败而第一个程序没有? 或者更好的是如何修复第二个?

我的gcc是i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00) ,我的内核是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

编辑:要清楚,我明白我想做的是愚蠢的。 此代码不会在好奇号流动站或纽约证券交易所上运行。 我试图更多地了解(GNU)C中的函数指针如何工作,并解释我发现的一些有趣的东西。 我保证在现实世界中永远不会做这样的事情。

一个有趣的问题,我在所引用的答案中看了一下这篇论文( C / C ++ / Objective-C中使用Curried函数的更多function可重用性 )。

因此,以下是您可能想要去的地方的建议路径。 我不认为这是一个真正的Curried函数,因为我不完全理解论文的内容,而不是函数式程序员。 但是,做了一些工作,我发现了一些有趣的应用程序。 另一方面,我不确定这是你想要的,有点让我觉得你可以用C做到这一点。

似乎有两个问题。

首先是能够使用任意参数列表处理任意函数调用。 我采用的方法是使用标准C库变量参数function(va_list与va_start(),va_arg()和va_end()函数),然后将函数指针与提供的参数一起存储到数据区域中,以便它们然后可以在以后执行。 我借用并修改了printf()函数如何使用格式行来知道提供了多少参数及其类型。

接下来是函数及其参数列表的存储。 我只是使用了一个任意大小的结构来试验这个概念。 这需要更多的思考。

此特定版本使用一个被视为堆栈的数组。 有一个函数可用于将一些任意函数及其参数推送到堆栈数组中,并且有一个函数可以从堆栈数组中弹出最顶层的函数及其参数并执行它。

然而,实际上你可以在某种集合中使用任意结构对象,例如哈希映射,这可能非常酷。

我刚从论文中借用了信号处理程序示例,以表明该概念适用于那种应用程序。

所以这是源代码,我希望它可以帮助您找到解决方案。

您需要向交换机添加其他案例,以便能够处理其他参数类型。 我只是做了一些概念validation。

此外,这不会调用函数的函数,虽然表面看起来似乎是一个相当简单的扩展。 就像我说的,我并没有完全得到这个Curried的东西。

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

您可以使用的pushFunction()是在pushFunction()调用的函数中使用pushFunction()函数,以使您可以使用其参数将另一个函数放入堆栈。

例如,如果您修改上面的源中的函数otherFunc() ,如下所示:

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

如果你再添加一个doFuncAndPop()调用,你会看到第一个执行otherFunc() ,然后执行在otherFunc()中调用的myFunc()调用,最后执行在main ()推送的myFunc()调用main ()被称为。

编辑2:如果我们添加以下函数,这将执行已放入堆栈的所有函数。 这将允许我们基本上通过将函数和参数推送到我们的堆栈然后执行一系列函数调用来创建一个小程序。 这个函数也允许我们在没有任何参数的情况下推送一个函数,然后推送一些参数。 当从我们的堆栈弹出函数时,如果参数块没有有效的函数指针,那么我们要做的是将该参数列表放在堆栈顶部的参数块上然后执行它。 也可以对上面的函数doFuncAndPop()进行类似的更改。 如果我们在执行的函数中使用pushFunction()操作,我们可以做一些有趣的事情。

实际上,这可能是一个螺纹口译员的基础。

 // 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:然后我们更改pushFunc()以使用以下附加开关处理double:

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

因此,通过这个新function,我们可以执行以下操作。 首先创建我们的两个函数类似于原始问题。 我们将在一个函数中使用pushFunction()来推送随后由堆栈上的下一个函数使用的参数。

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

新的我们使用以下一系列语句的新function:

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

这些语句首先执行函数g2() ,其值为4.5,然后函数g2()将其返回值推送到我们的堆栈上,由函数f1()使用,该函数首先在我们的堆栈上推送。

你试图依赖未定义的行为:一旦内部函数超出范围,因为外部函数确实退出,通过某个指针调用该内部函数的行为是未定义的。 什么都可能发生。 事物意外地适用于整数情况并不意味着你可以期望相同的double,或者你甚至可以期望int在不同的编译器,不同的编译器版本,不同的编译器标志或不同的目标体系结构上相同。

所以不要依赖未定义的行为。 当事实上你对这些警告采取行动时,不要声称“注意警告”。 警告明确指出:

如果你试图在包含函数退出后通过其地址调用嵌套函数,那么所有的地狱都会破裂。

C中没有闭合 ,所以在这个意义上不能有cur。 如果将一些数据传递给函数调用,则可以获得类似的效果,但这看起来不像普通的函数调用,所以它不会像普通的currying一样。 C ++在那里具有更大的灵活性,因为它允许对象在语法上像函数一样运行。 在C ++世界中,currying通常被称为函数参数的“ 绑定 ”。

如果你真的想知道为什么一段代码工作但另一段代码失败,你可以获取汇编代码(例如通过gcc -S -fverbose-asm )并模拟头脑中的执行,看看你的数据会发生什么和东西。 或者您可以使用调试器查看失败的位置或数据位置的变化。 可能需要一些工作,我怀疑这是值得的。

请原谅我没有得到它,但是你为什么不换行而不是咖喱 ,因为你在编译时声明了函数呢? currying的优点是 – 或者至少在我看来 – 你可以在运行时定义一个部分应用的函数,但在这里你不是这样做的。 或者我错过了什么?

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

修复上述代码的版本

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

通过示例参数示例了解不同的操作函数

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