C中的高阶函数

时间:2010-03-29 03:20:27

标签: c functional-programming higher-order-functions

是否有一种“正确”的方式在C中实现更高阶的功能。

我对这里的可移植性和语法正确性等问题非常好奇,如果有多种方法,那么优点和缺点是什么。

编辑: 我想知道如何创建更高阶函数的原因是我编写了一个系统来将PyObject列表(在调用python脚本时得到)转换为包含相同数据但以不依赖于的方式组织的C结构列表python.h库。所以我的计划是有一个函数,它遍历pythonic列表并在列表中的每个项目上调用一个函数,并将结果放在一个列表中然后返回。

所以这基本上是我的计划:

typedef gpointer (converter_func_type)(PyObject *)

gpointer converter_function(PyObject *obj)
{
    // do som stuff and return a struct cast into a gpointer (which is a void *)
}

GList *pylist_to_clist(PyObject *obj, converter_func_type f)
{
   GList *some_glist;
   for each item in obj
   {
       some_glist = g_list_append(some_glist, f(item));
   }
   return some_glist;
}

void some_function_that_executes_a_python_script(void)
{
   PyObject *result = python stuff that returns a list;
   GList *clist = pylist_to_clist(result, converter_function);
}

并澄清问题:我想知道如何以更安全和更正确的方式做到这一点。我真的想保持更高阶的功能风格,但如果这是不赞成的,我非常感谢如何做到这一点其他一些方式。

8 个答案:

答案 0 :(得分:19)

从技术上讲,高阶函数只是获取或返回函数的函数。所以像qsort这样的东西已经是高阶的。

如果你的意思更像是在函数式语言中找到的lambda函数(高阶函数真正变得有用的话),那么这些函数要难得多,而且在当前标准C中无法自然完成。它们只是不是语言的一部分。 Apple的区块扩展是最佳候选者。它只适用于GCC(和LLVM的C编译器),但它们确实非常有用。希望这样的东西会流行起来。以下是一些相关资源:

答案 1 :(得分:7)

在C中实现高阶函数的一个大问题是,要做任何非平凡的事情,你需要闭包,这些函数指针是用包含他们有权访问的局部变量的数据结构扩充的。因为闭包背后的整个想法是捕获局部变量并将它们与函数指针一起传递,所以没有编译器支持就很难做到。即使有编译器支持,如果没有垃圾收集也很难做到,因为变量可能存在于其范围之外,因此很难确定何时释放它们。

答案 2 :(得分:4)

在直接c中,这实际上只是通过函数指针来完成,这既是痛苦也不是对这类事物的意义(这部分是为什么它们很痛苦)。然而,块(或非闭合,根据非苹果)是非常好的。他们用gcc-4.x或者其他东西编译,然后用icc编译,但无论你想要什么。不幸的是,我似乎无法在网上找到任何好的教程,但足以说它的工作原理如下:

void iterate(char *str, int count, (^block)(str *)){
  for(int i = 0; i < count; i++){
    block(list[i]);
  }
}

main() {
  char str[20];
  iterate(str, 20, ^(char c){
    printf("%c ", c);
  });

  int accum = 0;
  iterate(someList, 20, ^(char c){
    accum += c;
    iterate(str, 20, ^(char c){
      printf("%c ", c);
    });
  });
}
很明显,这段代码没有意义,但是它打印一个字符串(str)的每个字符,在它之间有一个空格,然后将所有字符一起添加到accum中,每次它都打印出字符列表试。

希望这会有所帮助。顺便说一下,在Mac OS X Snow Leopard api中,块是非常明显的,我相信它们是即将推出的C ++ 0x标准,所以它们并不是那么不寻常。

答案 3 :(得分:4)

如果你热衷于在普通的C中这样做,你需要记住包括从仿函数的调用者(高阶函数)传递一个上下文指针到传入的函数的选项。你模拟了足够的闭合,你可以让事情足够容易。那个指针指向的......好吧,这取决于你,但它应该是仿函数API中的void*(或者它的众多别名之一,例如GLib世界中的gpointer或Tcl C API中的ClientData

[编辑]:使用/改编你的例子:

typedef gpointer (converter_func_type)(gpointer,PyObject *)

gpointer converter_function(gpointer context_ptr,PyObject *obj)
{
    int *number_of_calls_ptr = context_ptr;
    *number_of_calls_ptr++;
    // do som stuff and return a struct cast into a gpointer (which is a void *)
}

GList *pylist_to_clist(PyObject *obj, converter_func_type f, gpointer context_ptr)
{
   GList *some_glist;
   for each item in obj
   {
       some_glist = g_list_append(some_glist, f(context_ptr,item));
   }
   return some_glist;
}

void some_function_that_executes_a_python_script(void)
{
   int number_of_calls = 0;
   PyObject *result = python stuff that returns a list;
   GList *clist = pylist_to_clist(result, converter_function, &number_of_calls);
   // Now number_of_calls has how often converter_function was called...
}

这是一个如何做到这一点的简单例子,但它应该向你展示道路。

答案 4 :(得分:3)

实际上任何有趣的高阶函数应用程序都需要闭包,在C中需要手动定义和填充结构函数参数的繁琐且容易出错的例程。

答案 5 :(得分:2)

这是一个问题的答案:如何在C中编写函数,这里重定向。

您可以创建数据结构来实现列表数据类型。 该结构可以包含函数指针。

#include<stdlib.h>
#include<malloc.h>

typedef (*fun)();

typedef struct funList { fun car; struct funList *cdr;} *funList;

const funList nil = NULL;

int null(funList fs){ return nil==fs; }

fun car(funList fs)
{
   if(!null(fs)) return fs->car; 
   else 
   {
     fprintf(stderr,"error:can't car(nil) line:%d\n",__LINE__);
     exit(1);
   }
}

funList cdr(funList ls)
{ if(!null(ls)) return ls->cdr; 
  else 
  {
    fprintf(stderr,"error:can't cdr(nil) line:%d\n",__LINE__);
    exit(1);
  }
}

funList cons(fun f, funList fs)
{  funList ls;

   ls=(funList) malloc(sizeof(struct funList));
   if(NULL==ls)
   {
     fprintf(stderr,"error:can't alloc mem for cons(...) line:%d\n",__LINE__);
     exit(1);
   }

   ls->car=f;
   ls->cdr=fs;

   return ls;
}

我们可以编写一个应用函数列表的函数comp:

type_2 comp(funList fs, type_1 x)
{  
   return (null(fs)) ? x : car(fs)(comp(cdr(fs),x)); 
}

它是如何工作的一个例子。我们使用(f g h)作为cons的缩写符号(f,cons(g,cons(h,nil))),它应用于给定的参数x:

comp((f g h),x)

=

f(comp((g h),x))

=

f(g(comp((h),x)))

=

f(g(h(comp(nil,x))))

=

f(g(h(x)))

如果您在SML或Haskell等类型语言中使用了多态列表类型,则comp的类型应为:

comp :: ([a -> a],a) -> a

因为在该上下文中,列表中的所有成员都具有相同的类型。 从这个意义上说,C可以更灵活。也许像是

typedef void (*fun)();

typedef (*fun)();

你应该看看C手册对此有何看法。并确保所有连续的函数都具有兼容的类型。

要撰写的函数应该是纯粹的,即没有副作用,也没有自由变量。

答案 6 :(得分:1)

在C语言中很难做到。在C ++中更有可能(参见functors tutorial或Boost的bindfunction库)。最后,C++0x adds native support for lambda functions,它会照顾你关闭你的功能所依赖的所有变量。

答案 7 :(得分:0)

如果您想创建更高阶的功能,请不要使用C.您的问题有C解决方案。它们可能并不优雅,或者它们可能会更加优雅。

[编辑]我建议实现这一目标的唯一方法是使用脚本语言。其他人打电话给我。所以,我用这个替换了这个建议:[/ Edit]

你想要达到什么目的?如果你想模仿闭包,请使用支持它们的语言(你可以通过库绑定Ruby,lua,javascript等)。如果你想使用回调函数,函数指针就可以了。函数指针结合了C的最危险区域(指针和弱类型系统),所以要小心。函数指针声明也不容易阅读。

你找到一些使用函数指针的C库,因为它们必须这样做。如果您正在编写库,也许您也需要使用它们。如果您只是在自己的代码中使用它们,那么您可能不会在C中思考。您正在考虑使用lisp或scheme或ruby或...并尝试用C语言编写。学习C语言。