现代C++函数式编程

链接 http://geek.csdn.net/news/detail/96636

概述
函数式编程是一种编程范式,它有下面的一些特征:

函数是一等公民,可以像数据一样传来传去。
高阶函数
递归
pipeline
惰性求值
柯里化
偏应用函数
C++98/03中的函数对象,和C++11中的Lambda表达式、std::function和std::bind让C++的函数式编程变得容易。我们可以利用C++11/14里的新特性来实现高阶函数、链式调用、惰性求值和柯理化等函数式编程特性。本文将通过一些典型示例来讲解如何使用现代C++来实现函数式编程。

高阶函数和pipeline的表现形式
高阶函数就是参数为函数或返回值为函数的函数,经典的高阶函数就是map、filter、fold和compose函数,比如Scala中高阶函数:

map

numbers.map((i: Int) => i * 2)
对列表中的每个元素应用一个函数,返回应用后的元素所组成的列表。

filter

numbers.filter((i: Int) => i % 2 == 0)
移除任何对传入函数计算结果为false的元素。

fold

numbers.fold(0) { (z, i) =>
a + i
}
将一个初始值和一个二元函数的结果累加起来。

compose

val fComposeG = f _ compose g _
fComposeG(“x”)
组合其它函数形成一个新函数f(g(x))。

上面的例子中,有的是参数为函数,有的是参数和返回值都是函数。高阶函数不仅语义上更加抽象泛化,还能实现“函数是一等公民”,将函数像data一样传来传去或者组合,非常灵活。其中,compose还可以实现惰性求值,compose的返回结果是一个函数,我们可以保存起来,在后面需要的时候调用。

pipeline把一组函数放到一个数组或是列表中,然后把数据传给这个列表。数据就像一个链条一样顺序地被各个函数所操作,最终得到我们想要的结果。它的设计哲学就是让每个功能就做一件事,并把这件事做到极致,软件或程序的拼装会变得更为简单和直观。
Scala中的链式调用是这样的:

s(x) = (1 to x) |> filter (x => x % 2 == 0) |> map (x => x * 2)
用法和Unix Shell的管道操作比较像,|前面的数据或函数作为|后面函数的输入,顺序执行直到最后一个函数。

这种管道方式的函数调用让逻辑看起来更加清晰明了,也非常灵活,允许你将多个高阶函数自由组合成一个链条,同时还可以保存起来实现惰性求值。现代C++实现这种pipeline也是比较容易的,下面来讲解如何充分借助C++11/14的新特性来实现这些高阶函数和pipeline。

实现pipeline的关键技术
根据前面介绍的pipeline表现形式,可以把pipeline分解为几部分:高阶函数,惰性求值,运算符|、柯里化和pipeline,把这几部分实现之后就可以组成一个完整的pipeline了。下面来分别介绍它们的实现技术。

高阶函数
函数式编程的核心就是函数,它是一等公民,最灵活的函数就是高阶函数,现代C++的算法中已经有很多高阶函数了,比如for_each, transform:

std::vector vec{1,2,3,4,5,6,7,8,9}
//接受一个打印的Lambda表达式
std::for_each(vec.begin(), vec.end(), [](auto i){ std::cout<

define define_functor_type(func_name) class tfn_##func_name {\

public: template

define make_globle_functor(NAME, F) const auto NAME = define_functor_type(F);

//test code
make_globle_functor(fn_add, add);
make_globle_functor(fn_add_one, add_one);

int main()
{
fn_add(1, 2);
fn_add_one(1);

return 0;   

}
make_globle_functor生成了一个可以直接使用的全局函数对象,使用起来更方便了。用这个方法就可以将普通函数转成pipeline中的函数对象了。接下来我们来探讨实现惰性求值的关键技术。

惰性求值
惰性求值是将求值运算延迟到需要值时候进行,通常的做法是将函数或函数的参数保存起来,在需要的时候才调用函数或者将保存的参数传入函数实现调用。现代C++里已经提供可以保存起来的函数对象和lambda表达式,因此需要解决的问题是如何将参数保存起来,然后在需要的时候传给函数实现调用。我们可以借助std::tuple、type_traits和可变模版参数来实现目标。

template

define tfn_chain fn_chain<>()

//test code
void test_pipe()
{
auto f1 = [](int x) { return x + 3; };
auto f2 = [](int x) { return x * 2; };
auto f3 = [](int x) { return (double)x / 2.0; };
auto f4 = [](double x) { std::stringstream ss; ss << x; return ss.str(); };
auto f5 = [](string s) { return “Result: ” + s; };
auto compose_fn = tfn_chain|f1|f2|f3|f4|f5; //compose a chain
compose_fn(2); // Result: 5
}
测试代码中用一个fn_chain和运算符|将所有的函数组合成了一个函数链,在需要的时候调用,从而实现了惰性求值。

fn_chain的实现思路是这样的:内部有一个std::tuple

template

define make_globle_curry_functor(NAME, F) define_functor_type(F); const auto NAME = fn_to_curry_functor(tfn_##F());

make_globle_curry_functor(map, fn_map);
make_globle_curry_functor(reduce, fn_reduce);
make_globle_curry_functor(filter, fn_filter);
我们定义了map、reduce和filter支持柯里化的三个全局函数对象,接下来我们就可以把它们组成一个pipeline了。

void test_pipe()
{
//test map reduce
vector slist = { “one”, “two”, “three” };

slist | (map >> [](auto s) { return s.size(); })
    | (reduce >> 0 >> [](auto a, auto b) { return a + b; })
    | [](auto a) { cout << a << endl; };

//test chain, lazy eval
auto chain = tfn_chain | (map >> [](auto s) { return s.size(); })
    | (reduce >> 0 >> [](auto a, auto b) { return a + b; })
    | ([](int a) { std::cout << a << std::endl; });

slist | chain;

}
上面的例子实现了pipeline的mapreduce,这个pipeline支持currying还可以任意组合,非常方便和灵活。

有了这个pipeline,实现灵活的AOP也是很容易的:

struct person
{
person get_person_by_id(int id)
{
this->id = id;
return *this;
}

int id;
std::string name;

};
void test_aop()
{
const person& p = { 20, “tom” };
auto func = std::bind(&person::get_person_by_id, &p, std::placeholders::_1);
auto aspect = tfn_chain | ([](int id) { cout << “before”; return id + 1; })
| func
| ([](const person& p) { cout << “after” << endl; });

aspect(1);

}
上面的测试例子中,核心逻辑是func函数,我们可以在func之前或之后插入切面逻辑,切面逻辑可以不断地加到链条前面或者后面,实现很巧妙,使用很常灵活。

总结
本文通过介绍函数式编程的概念入手,分析了函数式编程的表现形式和特性,最终通过现代C++的新特性和一些模版元技巧实现了一个非常灵活的pipeline,展示了现代C++实现函数式编程的方法和技巧,同时也提现了现代C++的强大威力和无限的可能性。文中完整的代码可以从我的GitHub(https://github.com/qicosmos/cosmos/blob/master/modern_functor.hpp)上查看。

本文的代码和思路参考和借鉴了http://vitiy.info/templates-as-first-class-citizens-in-cpp11/,在此表示感谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值