现代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/,在此表示感谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/242736.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习之正则化

根据上一篇博客《统计学习概论》可以知道&#xff0c;正则化的作用是选择经验风险和模型复杂度同时较小的模型。下面从过拟合的角度来理解正则化。 #过拟合问题 例子说明&#xff0c;线性回归问题&#xff08;房价&#xff09; 分析&#xff1a; 1&#xff09;左边第一幅图&am…

python开发平台Ubuntu

python开发平台Ubuntu APT软件管理和远程登录

a.out、coff、elf三种文件格式

转自&#xff1a;http://blog.chinaunix.net/uid-11469366-id-1747286.html 补充&#xff1a;a.out早期并不是elf格式的&#xff0c;而是unix下另一种可执行格式&#xff0c;新的a.out是 本文讨论了 UNIX/LINUX 平台下三种主要的可执行文件格式&#xff1a;a.out&#xff08;…

投影矩阵

作者&#xff1a;桂。 时间&#xff1a;2017-10-19 06:02:00 链接&#xff1a;http://www.cnblogs.com/xingshansi/p/7690292.html 前言 最近在交替投影算法中&#xff0c;用到投影矩阵&#xff0c;简单记录。 一、投影矩阵定义 此处以列满秩为例&#xff0c;行满秩可依次类推…

不同维度极值点查找

作者&#xff1a;桂。 时间&#xff1a;2017-10-19 17:00:12 链接&#xff1a;http://www.cnblogs.com/xingshansi/p/7693557.html 前言 主要梳理不同维度信号极值点的查找思路。 一、思想 思想都是一个&#xff0c;通过极值点的定义&#xff1a;数值高于相邻的点&#xff0c…

俯仰角/偏航角的转化

作者&#xff1a;桂。 时间&#xff1a;2017-10-20 10:29:52 链接&#xff1a;http://www.cnblogs.com/xingshansi/p/7698237.html 前言 主要记录坐标系的转化。 一、坐标转化 对于坐标系&#xff1a; 设方位角为φ&#xff0c;俯仰角为theta&#xff0c;仰角β与偏航角α&am…

安装Centos8.1

安装Centos8.1 按回车

Ubuntu 14.04 LTS 下升级 gcc 到 gcc-4.9、gcc-5 版本

转载&#xff1a; http://www.cnblogs.com/BlackStorm/p/5183490.html Ubuntu 14.04 LTS 下升级 gcc 到 gcc-4.9、gcc-5 版本 如果没记错的话&#xff0c;阿里云ECS上的Ubuntu也是LTS版本。 如果还在使用较旧版本的Ubuntu&#xff0c;或者是Ubuntu LTS&#xff0c;那么我们是…

SVM学习——在matlab上安装libsvm库(一)

环境搭建平台&#xff1a; Windows PCMATLAB 软件libsvm库&#xff08;SVM工具箱&#xff09;安装步骤 准备工作 安装对应的编译器&#xff0c;在网站上查看当前matlab版本支持的编译器版本。本文使用的matlab 2015a版本&#xff08;支持编译器详情&#xff09;。通过查询&…

正则表达式练习笔记

下面的内容是一个 data1.txt 文本内容&#xff0c;里面记录了一些正则表达式的笔记 long long ago there is girl, shes name is little redhat.. long_long_long#long;long:long This is a test txt... my phone number is 18621735531There are a lot of good books,220123 …

逾期后,如何修复个人征信?

个人征信大家都是知道很宝贵&#xff0c;但是有些朋友会在有意无意之间造成逾期&#xff0c;结果给申请贷款、申请信用卡带来了诸多不便。逾期是谁也不想看到的&#xff0c;但是如果逾期已经发生了&#xff0c;我们就无法改变&#xff0c;唯一能做的就是努力去修复&#xff0c;…

空间谱专题16:信号个数估计

作者&#xff1a;桂。 时间&#xff1a;2017-10-24 21:50:16 链接:http://www.cnblogs.com/xingshansi/p/7726082.html 前言 记录阵列信号在DOA估计中&#xff0c;信源个数估计的基本方法。 一、基本估计方法 参考&#xff1a;王永良《空间谱估计》p42: 以MDL为例&#xff1a…

根据verilog代码画电路图

根据verilog代码画电路图 FPGA设计的本质是硬件设计&#xff0c;而且verilog是描述硬件设计的语言&#xff08;也就是描述电路&#xff09;&#xff0c;一个标准的工程师需要学会建立电路和Verilog对应的关系&#xff0c;学会看到电路图&#xff0c;就能写出相应的Verilog代码…

VS2015编译boost 1.62.0

参考链接&#xff1a; http://blog.chinaunix.net/uid-22301538-id-3158997.html D:\boost_1_62_0>bjam –toolsetmsvc-14.0 –prefixD:/boost_1_62_0/output –without-python –build-typecomplete linkshared threadingmulti install

复数矩阵分解的拆解思路(矩阵求逆/特征值分解)

作者&#xff1a;桂。 时间&#xff1a;2017-10-26 07:11:02 链接&#xff1a;http://www.cnblogs.com/xingshansi/p/7735016.html 前言 主要记录特征值分解的硬件实现思路。 一、实数矩阵转化 在FPGA运算中&#xff0c;对实数运算通常优于对复数运算。假设C为复数矩阵&#…

贷款机构如何审核个人征信?

贷款是要查看征信的&#xff0c; 但是大家知道贷款机构是如何审查借款人的征信吗&#xff1f;下面我们一起来看下。1看征信报告打印时间一般贷款机构要求提供的是最新的征信报告&#xff0c;当然每个机构要的具体期限不一样&#xff0c;有的要求是最近一个月的&#xff0c;有的…

COMS技术

COMS技术 n型MOS(NMOS)三极管的结构如图所示,该图不是按照实际比例绘制的。三极管的衬底是被掺杂后成为p型半导体材料的硅晶片。NMOS衬底的厚度远比其他三极管要厚。在每个三极管上有两个区域,被掺入大量的杂质,成为n型半导体区域。这两个区域分别形成了三极管的源极和漏极…

VS2015 + CUDA 8.0 配置GTX1070的OpenCL 开发环境

一、查看计算机对OpenCL异构计算的支持情况 使用 GPU Caps Viewer 查看计算机对OpenCL的支持情况&#xff0c;目前最新的版本是 gpu-caps-viewer-1-32-0&#xff0c; 下载地址&#xff1a;http://www.geeks3d.com/20161107/gpu-caps-viewer-1-32-0-released/ 从上面两个图可以…

空间谱专题16:间距选取分析

作者&#xff1a;桂。 时间&#xff1a;2017-11-01 23:26:30 链接&#xff1a;http://www.cnblogs.com/xingshansi/p/7769153.html 前言 本文主要分析布阵间距选取依据&#xff0c;个人观点&#xff0c;仅供参考。 一、问题描述 对于空间谱测向&#xff08;以MUSIC算法为例&a…