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 col{20,24,37,42,23,45,37};
std::for_each(col,lambda_echo);

//下面是函数式编程里面的for_each 
template
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 
Collection map(Collection col,unop op) 
{
    std::transform(col.begin(),col.end(),col.begin(),op);
    return col;
}

reduce
改变来原始的容器元素

//transform实现reduce
//这里的参数是复制过来的,所以在函数里面修改col容器,并不影响原始数据;
template 
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 
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 
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 
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 
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++

你可能感兴趣的:(C++11 函数式编程)