有哪些工具可以用C进行功能编程?


153

最近我一直在思考如何使用C(而不是 C ++)进行函数式编程。显然,C是一种过程语言,并不真正支持本机功能编程。

是否有任何编译器/语言扩展在语言中添加了一些功能性的编程构造?GCC提供嵌套功能作为语言扩展;嵌套函数可以从父堆栈框架访问变量,但这距离成熟的闭包还有很长的路要走。

例如,我认为在C语言中可能真正有用的一件事是,在需要使用函数指针的任何地方,您都可以传递lambda表达式,从而创建一个衰减为函数指针的闭包。C ++ 0x将包含lambda表达式(我认为它很棒);但是,我正在寻找适用于直C的工具。

为了明确起见,我并不是要解决C语言中更适合函数编程的特定问题。我只是想知道如果要使用哪些工具。


1
除了寻找黑客和非便携式扩展来试图将C变成不是它的东西之外,您为什么不只使用一种提供所需功能的语言呢?
罗伯特·格兰伯

另请参阅相关问题:< stackoverflow.com/questions/24995/… >
安迪·布里斯

@AndyBrice这个问题是关于另一种语言的,实际上似乎没有任何意义(C ++的“生态系统”没有与之相同的含义。)
Kyle Strand,

Answers:


40

FFCALL使您可以在C中构建闭包- callback = alloc_callback(&function, data)返回callback(arg1, ...)等于调用的函数指针function(data, arg1, ...)。但是,您将必须手动处理垃圾收集。

相关地,已将添加到Apple的GCC分支中;它们不是函数指针,但是它们使您能够在传递lambda的同时避免手动构建和释放已捕获变量的存储空间(有效地,发生了一些复制和引用计数,隐藏在某些语法糖和运行时库的后面)。


89

您可以使用GCC的嵌套函数来模拟lambda表达式,实际上,我有一个宏可以为我做这件事:

#define lambda(return_type, function_body) \
  ({ \
    return_type anon_func_name_ function_body \
    anon_func_name_; \
  })

像这样使用:

int (*max)(int, int) = lambda (int, (int x, int y) { return x > y ? x : y; });

12
这真的很酷。这似乎__fn__是块中定义的函数只是任意名称({...... }),而不是一些GCC扩展或预定义宏?名称的选择__fn__(看起来非常类似于GCC定义)确实让我抓狂,搜索GCC文档没有任何效果。
FooF 2012年

1
可悲的是,这在实践中行不通:如果您希望闭包捕获任何内容,则需要可执行堆栈,这是一个糟糕的安全实践。我个人认为这根本没有用。
黛咪

它不是远程有用的,但是很有趣。这就是为什么其他答案被接受的原因。
乔D

65

函数式编程与lambda无关,而是与纯函数有关。因此,以下内容广泛推广了功能样式:

  1. 仅使用函数参数,不使用全局状态。

  2. 最小化副作用,即printf或任何IO。返回描述IO的数据,该数据可以执行而不是直接在所有功能中引起副作用。

这可以用纯C语言实现,不需要魔术。


5
我认为您将函数式编程的这种极端观点视为荒谬。例如,map如果没有规范将功能传递给它的纯规范有什么用?
乔纳森·伦纳德

27
我认为这种方法比使用宏在c中创建功能性语言更为实用。与使用map而不是for循环相比,适当地使用纯函数更可能改善系统。
Andy Till 2014年

6
您当然知道地图只是起点。如果没有一流的功能,则任何程序(即使其所有功能都是“纯”的)(与信息功能一样)都将比具有一流功能的程序低阶。我认为,只有FP的主要优点是,只有一流的函数才有可能使用这种声明式样式。我认为您对某人不利,暗示FP必须提供所有由于缺少一流功能而引起的冗长。应该注意的是,从历史上看,FP一词所代表的是一流的功能,而不仅仅是纯度。
乔纳森·伦纳德

PS以前的评论来自移动设备-不规则语法不是故意的。
乔纳森·伦纳德

1
安迪·蒂尔斯(Andy Tills)的答案显示出非常务实的看法。在C语言中达到“纯净”并不需要花哨的钱,而且会带来很大的好处。相比之下,头等舱功能为“舒适生活”。它很方便,但是采用纯编程风格是可行的。我不知道为什么没有人讨论与这一切有关的尾声。那些想要一流功能的人也应该要求尾叫。
BitTickler '18

17

如今,Hartel&Muller的书《Functional C》可以在(2012-01-02)上找到:http : //eprints.eemcs.utwente.nl/1077/(有指向PDF版本的链接)。


2
本书没有尝试任何功能(与问题相关)。
尤金·托尔马切夫

4
看来你是对的。确实,序言指出,本书的目的是在学生已经熟悉函数式编程之后教命令式编程。仅供参考,这是我在SO中的第一个答案,它之所以被写为答案,仅是因为我并没有声誉要求以烂链接评论以前的答案。我总是想知道为什么人们对这个答案+1了……请不要那样做!:-)
FooF,2014年

1
也许这本书应该被更好地称为“功能后编程”(首先是因为“ Imperative C”听起来不那么性感,其次是因为它假定对函数式编程范式的熟悉,其次是双关语,因为命令式范式似乎像是标准的衰落,正确的功能,也许是第四个提到拉里·沃尔(Larry Wall)的后现代编程概念的方法-尽管这本书是在拉里·沃尔(Larry Wall)的文章/演讲之前写的。
FooF,2014年

这是重复的答案
PhilT

8

函数式编程风格的前提是一流的功能。如果您愿意,可以在便携式C语言中对其进行仿真:

  • 手动管理词汇范围绑定,也称为闭包。
  • 手动管理功能变量的生命周期。
  • 函数应用程序/调用的替代语法。
/* 
 * with constraints desribed above we could have
 * good approximation of FP style in plain C
 */

int increment_int(int x) {
  return x + 1;
}

WRAP_PLAIN_FUNCTION_TO_FIRST_CLASS(increment, increment_int);

map(increment, list(number(0), number(1)); // --> list(1, 2)


/* composition of first class function is also possible */

function_t* computation = compose(
  increment,
  increment,
  increment
);

*(int*) call(computation, number(1)) == 4;

此类代码的运行时可能如下所示

struct list_t {
  void* head;
  struct list_t* tail;
};

struct function_t {
   void* (*thunk)(list_t*);
   struct list_t* arguments;
}

void* apply(struct function_t* fn, struct list_t* arguments) {
  return fn->thunk(concat(fn->arguments, arguments));
}

/* expansion of WRAP_PLAIN_FUNCTION_TO_FIRST_CLASS */
void* increment_thunk(struct list_t* arguments) {
  int x_arg = *(int*) arguments->head;
  int value = increment_int(x_arg);
  int* number = malloc(sizeof *number);

  return number ? (*number = value, number) : NULL;
}

struct function_t* increment = &(struct function_t) {
  increment_thunk,
  NULL
};

/* call(increment, number(1)) expands to */
apply(increment, &(struct list_t) { number(1), NULL });

从本质上讲,我们模仿的是一流的函数,其闭包表示为一对函数/自变量加一堆宏。完整的代码可以在这里找到。


7

我想到的主要是代码生成器的使用。您是否愿意使用提供功能编程的另一种语言进行编程,然后从中生成C代码?

如果这不是一个吸引人的选择,那么您可能会滥用CPP来参与其中。宏系统应该让您模仿一些功能编程思想。我听说过gcc是通过这种方式实现的,但我从未检查过。

C当然可以使用函数指针来传递函数,主要问题是缺少闭包,并且类型系统倾向于妨碍使用。您可以探索比CPP(例如M4)更强大的宏系统。我想最终,我的意思是,如果不付出很大的努力,真正的C并不能满足任务,但是您可以扩展C以使其适合任务。如果您使用CPP,则该扩展看起来最像C,也可以转到频谱的另一端,并从其他某种语言生成C代码。


这是最诚实的答案。尝试以功能方式编写C是与语言设计和结构本身作斗争。
josiah

5

如果要实现闭包,则必须精通汇编语言和堆栈交换/管理。不建议您这样做,只是说这是您必须要做的。

不确定如何在C中处理匿名函数。但是,在von Neumann机器上,您可以在asm中执行匿名函数。



2

费利克斯语言编译为C ++。如果您不介意C ++,也许这可以成为踏脚石。


9
⁻¹,因为α)作者明确提到了“非C ++”,β)编译器生成的C ++不会是“人类可读的C ++”。γ)C ++标准支持功能编程,无需使用从一种语言到另一种语言的编译器。
Hi-Angel

一旦通过宏或类似方法提出了一种功能语言,您就会自动暗示您对C不太在意。这个答案是正确的,因为C ++最初是作为C之上的宏派而成立的。走那条路足够远,您将获得一种新的语言。然后只涉及到后端讨论。
BitTickler '18

1

相当多的编程语言是用C语言编写的。其中一些支持作为一等公民的功能,该领域的语言为ecl(称为嵌入式Lisp IIRC),Gnu Smalltalk(gst)(Smalltalk具有块),然后还有库用于“关闭”,例如在glib2 http://library.gnome.org/devel/gobject/unstable/chapter-signal.html#closure中 它至少接近函数式编程。因此,也许可以使用其中一些实现进行功能编程。

好吧,或者您可以去学习Ocaml,Haskell,Mozart / Oz等;-)

问候


您介意告诉我,使用至少支持FP风格编程的库有什么严重的错误吗?
弗里德里希

1

我使用C进行函数式编程的方式是用C编写函数式语言解释器。我将其命名为Fexl,它是“函数表达语言”的缩写。

解释器非常小,在启用-O3的系统上可编译为68K。它也不是玩具-我正在将其用于我为自己的业务编写的所有新生产代码(基于网络的投资合作伙伴关系会计)。

现在,我仅编写C代码,是为了(1)添加一个调用系统例程的内置函数(例如fork,exec,setrlimit等),或者(2)优化一个可以用Fexl编写的函数(例如search) (用于子字符串)。

模块机制基于“上下文”的概念。上下文是将符号映射到其定义的函数(用Fexl编写)。读取Fexl文件时,可以使用任何喜欢的上下文来解析它。这使您可以创建自定义环境,或在受限制的“沙箱”中运行代码。

http://fexl.com


-3

您想使C成为函数,语法或语义的原因是什么?函数式编程的语义当然可以添加到C编译器中,但是到完成时,您基本上已经具有一种等效的现有函数式语言,例如Scheme,Haskell等。

仅学习直接支持那些语义的那些语言的语法将是更好的时间利用方式。


-3

不了解C。尽管Objective-C中有一些功能,但是OSX上的GCC也支持某些功能,但是我再次建议开始使用功能语言,上面提到了很多功能。我个人是从Scheme入手的,有一些很棒的书,例如《 The Little Schemer》可以帮助您做到这一点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.