AlexiaChen / AlexiaChen.github.io

My Blog https://github.com/AlexiaChen/AlexiaChen.github.io/issues
88 stars 11 forks source link

C++11之函数式风格编程 #33

Open AlexiaChen opened 4 years ago

AlexiaChen commented 4 years ago

title: C++11之函数式风格编程 date: 2017-03-3 10:48:40 tags:

C++是多范式编程语言,程序员可以选择多重范式的结合来编程,比如面向对象,面向过程,泛型编程,函数式特性。尤其是C++ 11标准中完善了函数式的一些特性,比如,lambda表达式,变参模版,新的STL函数bind。下面就讲讲怎样用C++ 11来写函数式风格的代码。

函数式风格编程

为什么要以函数式风格来编程

STL结合lambda表达式更加简洁高效


std::accumulate(std::begin(vec),std::end(vec),                            
         [](int a,int b){return a+b;});

模式匹配之模版元编程

// 编译时计算阶乘
template <int N>
struct Fac{ static int const val= N * Fac<N-1>::val; };
template <>
struct Fac<0>{ static int const val= 1; };

还有更好更简洁的编程风格

// 使用range for
for (auto v: vec) std::cout << v << std::endl;

什么是函数式编程

函数式编程的特点

一等函数

其实C语言也有类似的特性,函数指针,这样就有更强的表现能力,也明面上支持了回调。C++ 11直接支持lambda表达式了。就不需要那么复杂和不安全的函数指针了。

一等函数表达能力之分发表(Dispatch Table)

std::map<const char,std::function<double(double,double)>>  tab;

tab.insert(std::make_pair('+',[](double a,double b){return a + b;}));
tab.insert(std::make_pair('-',[](double a,double b){return a - b;}));
tab.insert(std::make_pair('*',[](double a,double b){return a * b;}));
tab.insert(std::make_pair('/',[](double a,double b){return a / b;}));

std::cout << "3.5+4.5= " << tab['+'](3.5,4.5) << std::endl;  //8
std::cout << "3.5*4.5= " << tab['*'](3.5,4.5) << std::endl;  // 15.75

tab.insert(std::make_pair('^',                                        
                  [](double a,double b){return pow(a,b);}));

std::cout << "3.5^4.5= " << tab['^'](3.5,4.5) << std::endl;  // 280.741

高阶函数

说到这里,可能有人懵了,一等函数和高阶函数有啥不一样?请看以下代码块就知道了:

function make_add(){
    var f = function (a, b){ return a + b;}; 
    return f;
}

以上代码,函数make_add就是高阶函数,匿名函数function(a,b)可以被bind到变量f上,或者本身也可以直接返回,所以这匿名函数就是一等函数。

所以,高阶函数用朴素的观点来理解的话,可以这么说:高阶函数是一等函数的应用

很多函数式编程语言,有三种经典的高阶函数应用直接体现:

很多编程语言已经有以上三种语义的支持了:

这三种语义有无数经典的应用场景:

Map + Reduce = MapReduce

Haskell

vec = [1 . . 9]
str = ["Programming","in","a","functional","style."]

map(\a → a^2) vec    -- [1,4,9,16,25,36,49,64,81]
map(\a -> length a) str -- [11,2,1,10,6]

filter(\x-> x<3 || x>8) vec  -- [1,2,9] 
filter(\x → isUpper(head x)) str -- [“Programming”]

foldl (\a b → a * b) 1 vec   -- 362800
foldl (\a b → a ++ ":" ++ b ) "" str -- “:Programming:in:a:functional:style.”

Python

vec = range(1,10)
str = ["Programming","in","a","functional","style."]

map(lambda x :  x*x , vec) # [1,4,9,16,25,36,49,64,81]
map(lambda x : len(x), str) # [11,2,1,10,6]

filter(lambda x: x<3 or x>8 , vec) # [1,2,9]
filter(lambda x: x[0].isupper(),str) # [“Programming”]

reduce(lambda a , b: a * b, vec, 1) # 362800
reduce(lambda a, b: a + b, str,"") # “:Programming:in:a:functional:style.”

C++ 11

std::vector<int> vec{1,2,3,4,5,6,7,8,9}
std::vector<std::string> str{"Programming","in","a","functional",       
                "style."}

std::transform(vec.begin(),vec.end(),vec.begin(),                 
        [](int i){ return i*i; }); // [1,4,9,16,25,36,49,64,81]
std::transform(str.begin(),str.end(),back_inserter(vec2),         
        [](std::string s){ return s.length(); }); // [11,2,1,10,6]

// [1,2,9]
vec.erase(std::remove_if(vec.begin(),vec.end(),
[](int i){ return !((i < 3) or (i > 8)) }),vec.end());
// [“Programming”]
str.erase(std::remove_if(str.begin(),str.end(),                   
        [](string s){ return !(isupper(s[0])); }),str.end());

std::accumulate(vec.begin(),vec.end(),1,                          
         [](int a, int b){ return a*b; }); // 362800

//“:Programming:in:a:functional:style.” 
std::accumulate(str.begin(),str.end(),string(""),                 
         [](string a,string b){ return a+":"+b; });      

纯函数

递归

C++ 11

// 编译时计算阶乘
template <int N>
struct Fac{ static int const val= N * Fac<N-1>::val; };
template <>
struct Fac<0>{ static int const val= 1; };

// 计算过程
Fac<3>::value = 3 * Fac<2>::value
              = 3 * 2 * Fac<1>::value
              = 3 * 2 * 1 * Fac<0>::value
              = 3 * 2 * 1 * 1
              = 6

同样的计算阶乘方式,我们用Haskell的模式匹配来实现以下:

fac 0 = 1
fac n = n * fac (n-1)

列表处理

比如:[1,2,3,4,5] head(x) = [1] tail(xs) = [2,3,4,5]

那么用模式匹配+递归写出如下Haskell代码:

-- 求和
mySum []     = 0
mySum (x:xs) = x + mySum xs
mySum [1,2,3,4,5]    -- 15

-- 计算过程
mySum [1,2,3,4,5] = 1 + mySum [2,3,4,5]
                  = 1 + 2 mySum [3,4,5]
                  = 1 + 2 + 3 mySum [4, 5]
                  = 1 + 2 + 3 + 4 mySum [5]
                  = 1 + 2 + 3 + 4 + 5 + mySum []
                  = 1 + 2 + 3 + 4 + 5 + 0
                  = 15

-- 实现map语义
myMap f [] = []
myMap f (x:xs)= f x: myMap f xs  
myMap (\x → x*x)[1,2,3] -- [1,4,9]

-- 计算过程
myMap (\x → x*x)[1,2,3] 
=> (\x -> x*x) 1 : myMap (\x -> x*x) [2,3]
=> 1 : (\x -> x*x) 2 : myMap (\x -> x*x) [3]
=> 1 : 4 : (\x -> x*x) 3 : myMap (\x -> x*x) []
=> 1 : 4 : 9 : []
=> [1,4,9]

下面用C++ 11 的变参模版来做以上的求和:

template<int ...> struct mySum;
template<>struct
mySum<>{
static const int value = 0;
};

template<int head, int ... tail> struct
mySum<head,tail...>{
static const int value= head + mySum<tail...>::value;
};

int sum = mySum<1,2,3,4,5>::value;  // 15

//计算过程
mySum<1,2,3,4,5>::value
=> 1 + mySum<2,3,4,5>::value
=> 1 + 2 + mySum<3,4,5>::value
=> 1 + 2 + 3 + mySum<4,5>::value
=> 1 + 2 + 3 + 4 + mySum<5>::value
=> 1 + 2 + 3 + 4 + 5 + mySum<>::value
=> 1 + 2 + 3 + 4 + 5 + 0
=> 15

其实呢,归而结网,列表处理的核心思想就是模式匹配。

再举个例子,用Haskell的模式匹配写一个乘法函数:

mult n 0 = 0
mult n m = (mult n (m – 1)) + n

-- 6
mult 3 2

--计算过程
mult 3 2
=> (mult 3 (2 - 1)) + 3
=> (mult 3 1) + 3
=> (mult 3 (1 - 1)) + 3 + 3
=> (mult 3 0) + 3 + 3
=> 0 + 3 + 3
=> 6

用C++ 11的模版元编程来写乘法:

template < int N1, int N2 > 
class Mult { 
  static const int value =  Mult<N1, N2 - 1>::value + N1; 
};

template < int N1 > 
class Mult <N1,0> { static const int value = 0; };

// 6
int result = Mult<3,2>::value;

//计算过程
Mult<3,2>::value
=> Mult<3,1>::value + 3
=> Mult<3,0>::value + 3 + 3
=> 0 + 3 + 3
=> 6

惰性求值