C++11 函数式编程

本文根据众多互联网博客内容整理后形成,引用内容的版权归原始作者所有,仅限于学习研究使用,不得用于任何商业用途。

当我们说起函数式编程来说,我们会看到如下函数式编程的长相:

  • 函数式编程的三大特性:
  • immutable data 不可变数据:像Clojure一样,默认上变量是不可变的,如果你要改变变量,你需要把变量copy出去修改。这样一来,可以让你的程序少很多Bug。因为,程序中的状态不好维护,在并发的时候更不好维护。(你可以试想一下如果你的程序有个复杂的状态,当以后别人改你代码的时候,是很容易出bug的,在并行中这样的问题就更多了)
  • first class functions:这个技术可以让你的函数就像变量一样来使用。也就是说,你的函数可以像变量一样被创建,修改,并当成变量一样传递,返回或是在函数中嵌套函数。这个有点像Javascript的Prototype(参看Javascript的面向对象编程
  • 尾递归优化:我们知道递归的害处,那就是如果递归很深的话,stack受不了,并会导致性能大幅度下降。所以,我们使用尾递归优化技术——每次递归时都会重用stack,这样一来能够提升性能,当然,这需要语言或编译器的支持。Python就不支持。
  • 函数式编程的几个技术
  • map & reduce :这个技术不用多说了,函数式编程最常见的技术就是对一个集合做Map和Reduce操作。这比起过程式的语言来说,在代码上要更容易阅读。(传统过程式的语言需要使用for/while循环,然后在各种变量中把数据倒过来倒过去的)这个很像C++中的STL中的foreach,find_if,count_if之流的函数的玩法。
  • pipeline:这个技术的意思是,把函数实例成一个一个的action,然后,把一组action放到一个数组或是列表中,然后把数据传给这个action list,数据就像一个pipeline一样顺序地被各个函数所操作,最终得到我们想要的结果。
  • recursing 递归 :递归最大的好处就简化代码,他可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。
  • currying:把一个函数的多个参数分解成多个函数, 然后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数这样,可以简化函数的多个参数。在C++中,这个很像STL中的bind_1st或是bind2nd。
  • higher order function 高阶函数:所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。
  • 还有函数式的一些好处
  • parallelization 并行:所谓并行的意思就是在并行环境下,各个线程之间不需要同步或互斥。
  • lazy evaluation 惰性求值:这个需要编译器的支持。表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值,也就是说,语句如x:=expression; (把一个表达式的结果赋值给一个变量)明显的调用这个表达式被计算并把结果放置到 x 中,但是先不管实际在 x 中的是什么,直到通过后面的表达式中到 x 的引用而有了对它的值的需求的时候,而后面表达式自身的求值也可以被延迟,最终为了生成让外界看到的某个符号而计算这个快速增长的依赖树。
  • determinism 确定性:所谓确定性的意思就是像数学那样 f(x) = y ,这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。而不是像程序中的很多函数那样,同一个参数,却会在不同的场景下计算出不同的结果。所谓不同的场景的意思就是我们的函数会根据一些运行中的状态信息的不同而发生变化。

上面的那些东西太抽象了,还是让我们来循序渐近地看一些例子吧。
我们先用一个最简单的例子来说明一下什么是函数式编程。

先看一个非函数式的例子:

int cnt;
void increment(){ 
  cnt++;
}

那么,函数式的应该怎么写呢?

int increment(int cnt){
  return  cnt+1;
}

你可能会觉得这个例子太普通了。是的,这个例子就是函数式编程的准则:不依赖于外部的数据,而且也不改变外部数据的值,而是返回一个新的值给你

我们再来看一个简单例子:

def inc(x):
  def incx(y):
    return x+y
  return incx

inc2 = inc(2)
inc5 = inc(5)

print inc2(5) # 输出 7
print inc5(5) # 输出 10

我们可以看到上面那个例子inc()函数返回了另一个函数incx(),于是我们可以用inc()函数来构造各种版本的inc函数,比如:inc2()和inc5()。这个技术其实就是上面所说的Currying技术。从这个技术上,你可能体会到函数式编程的理念:把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易读。

algorigthm头文件中定义了几个很有用的方法;

for_each
对于一些collection(vector,list,set,map)可以对其中每个元素执行后面的函数;

auto lambda_echo = [](int i ) { std::cout << i << std::endl; };
std::vector<int> col{20,24,37,42,23,45,37};
std::for_each(col,lambda_echo);

//下面是函数式编程里面的for_each 
template<typename Collection,typename Func>
void for_each(Collection const &collection,Func func)
{
    std::for_each(collection.begin(),collection.end(),func);
}

transform实现map
transform。
该算法用于实行容器元素的变换操作。
有如下两个使用原型,一个将迭代器区间[first,last)中元素,执行一元函数对象op操作,交换后的结果放在[result,result+(last-first))区间中。
另一个将迭代器区间[first1,last1)的元素i,依次与[first2,first2+(last-first))的元素j,执行二元函数操作binary_op(i,j),交换结果放在[result,result+(last1-first1)

//下面是函数式编程里面的map,因为c++的for_each不允许对象被修改,所以这里使用transform 
//map操作表示对容器中每个元素都执行相同的操作 
template <typename Collection,typename unop>
Collection map(Collection col,unop op) 
{
    std::transform(col.begin(),col.end(),col.begin(),op);
    return col;
}

reduce
改变来原始的容器元素

//transform实现reduce
//这里的参数是复制过来的,所以在函数里面修改col容器,并不影响原始数据;
template <typename Collection,typename binop>
auto reduce(Collection col,binop op) 
{
    //Collection result(col.size());
    std::transform(col.begin(),col.end()-1,col.begin()+1,col.begin()+1,op);
    return *(col.end()-1);
}

transform实现zip
zip函数将传进来的两个参数中相应位置上的元素组成一个pair数组。如果其中一个参数元素比较长,那么多余的参数会被删掉。

//zip操作表示对两个容器中对应元素执行操作,结果返回; 
template <typename Collection,typename binop>
Collection zip(Collection fc,Collection sc,binop op) 
{   
    //Collection result(fc.size());
    std::transform(fc.begin(),fc.end(),sc.begin(),fc.begin(),op);
    return fc;
}

find_if实现exists
表示判断集合中是否有某个元素符合条件;

// exists 表示判断集合中是否有某个元素符合条件; 
template <typename Collection,typename Condition>
bool exists(Collection col,Condition con) 
{
    auto exist = std::find_if(col.begin(),col.end(),con);
    return exist != col.end();
}

remove_if和erase实现filter
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
remove_if()并不会实际移除序列[start, end)中的元素; 如果在一个容器上应用remove_if(), 容器的长度并不会改变(remove_if()不可能仅通过迭代器改变容器的属性), 所有的元素都还在容器里面. 实际做法是, remove_if()将所有应该移除的元素都移动到了容器尾部并返回一个分界的迭代器. 移除的所有元素仍然可以通过返回的迭代器访问到.
为了实际移除元素, 你必须对容器自行调用erase()以擦除需要移除的元素.

//和map()类似,filter()也接收一个函数和一个序列。
//和map()不同的是,filter()把传入的函数依次作用于每个元素,
//然后根据返回值是True还是False决定保留还是丢弃该元素。
template <typename Collection,typename Predicate>
Collection filterNot(Collection col,Predicate predicate ) 
{   
    auto returnIterator = std::remove_if(col.begin(),col.end(),predicate);
    col.erase(returnIterator,std::end(col));    
    return col;
}

template <typename Collection,typename Predicate>
Collection filter(Collection col,Predicate predicate) 
{
     auto fnCol = filterNot(col,[predicate](typename Collection::value_type i) { return !predicate(i);});
     return fnCol; 
}

相应代码见: cpp11_21_for_cppFunctionProm.cpp

参考资料
函数式编程 | cpp11新特性详解与应用
是不是服务端编程刚开始都得从写业务开始? - 回答作者: itlr
函数式编程
简单的程序诠释C++ STL算法系列之十八:transform
std::remove_if
scala zip函数族详解
Functional programming in C++

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,881评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,052评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,598评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,407评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,823评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,872评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,037评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,778评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,505评论 1 247
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,745评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,233评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,231评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,141评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,939评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,954评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,784评论 2 275

推荐阅读更多精彩内容