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

13 c gcc types functional-programming function-pointers

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

#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\n",f1(g(2)));
}
Run Code Online (Sandbox Code Playgroud)

哪个按预期运行.但是,我原来的程序与doubles一起工作,所以我想我只是改变了适当的类型,我会没事的:

#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\n",f1(g(2)));
}
Run Code Online (Sandbox Code Playgroud)

这会产生如下:

bash-3.2$ ./a.out 
Segmentation fault: 11
bash-3.2$ ./a.out 
Illegal instruction: 4
Run Code Online (Sandbox Code Playgroud)

选择错误似乎是随机的.此外,如果使用编译-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.

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

Ric*_*ers 6

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

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

似乎有两个问题.

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

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

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

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

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

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

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

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

#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\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;
}
Run Code Online (Sandbox Code Playgroud)

您可以使用的另一个pushFunction()好处是在函数中使用该函数,该函数doFuncAndPop()可以使用其参数将另一个函数放入堆栈.

例如,如果您修改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);
}
Run Code Online (Sandbox Code Playgroud)

如果你再添加另一个调用doFuncAndPop()就会看到第一个otherFunc()被执行然后执行的调用就myFunc()otherFunc()执行了,最后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();
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑3: 然后我们进行更改以pushFunc()使用以下附加开关处理double:

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

因此,通过这个新功能,我们可以执行以下操作.首先创建我们的两个函数类似于原始问题.我们将在一个函数中使用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;
}
Run Code Online (Sandbox Code Playgroud)

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

double xDouble = 4.5;
pushFunction ((void (*)(...))f1, 0);
pushFunction ((void (*)(...))g2, "d", xDouble);
executeFuncStack();
Run Code Online (Sandbox Code Playgroud)

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