今天研究函数编程,拜读了一位大神的文章,函数式编程,做一下总结。首先什么是函数式编程?
函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。
再看看 Currying是什么?
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
我们先用一个最简单的例子来说明一下什么是函数式编程。
先看一个非函数式的例子:
1
2
3
4
|
int
cnt;
void
increment(){
cnt++;
}
|
那么,函数式的应该怎么写呢?
1
2
3
|
int
increment(
int
cnt){
return
cnt+1;
}
|
你可能会觉得这个例子太普通了。是的,这个例子就是函数式编程的准则:不依赖于外部的数据,而且也不改变外部数据的值,而是返回一个新的值给你。
我们再来看一个简单例子:
1
2
3
4
5
6
7
8
9
10
|
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技术。从这个技术上,你可能体会到函数式编程的理念:把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易读。
Map & Reduce
在函数式编程中,我们不应该用循环迭代的方式,我们应该用更为高级的方法,如下所示的Python代码
1
2
3
|
name_len
=
map
(
len
, [
"a"
,
"b"
,
"c"
])
print
name_len
# 输出 [3, 4, 9]
|
你可以看到这样的代码很易读,因为,这样的代码是在描述要干什么,而不是怎么干。
我们再来看一个Python代码的例子:
1
2
3
4
5
6
|
def
toUpper(item):
return
item.upper()
upper_name
=
map
(toUpper, [
"a"
,
"b"
,
"c"
])
print
upper_name
# 输出 ['HAO', 'CHEN', 'COOLSHELL']
|
顺便说一下,上面的例子个是不是和我们的STL的transform有些像?
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <iostream>
#include <algorithm>
#include <string>
using
namespace
std;
int
main() {
string s=
"hello"
;
string out;
transform(s.begin(), s.end(), back_inserter(out), ::
toupper
);
cout << out << endl;
// 输出:HELLO
}
|
在上面Python的那个例子中我们可以看到,我们写义了一个函数toUpper,这个函数没有改变传进来的值,只是把传进来的值做个简单的操作,然后返回。然后,我们把其用在map函数中,就可以很清楚地描述出我们想要干什么。而不会去理解一个在循环中的怎么实现的代码,最终在读了很多循环的逻辑后才发现原来是这个或那个意思。 下面,我们看看描述实现方法的过程式编程是怎么玩的(看上去是不是不如函数式的清晰?):
1
2
3
4
|
upname
=
[
'A'
,
'B'
,
'C'
]
lowname
=
[]
for
i
in
range
(
len
(upname)):
lowname.append( upname[i].lower() )
|
对于map我们别忘了lambda表达式:你可以简单地理解为这是一个inline的匿名函数。下面的lambda表达式相当于:def func(x): return x*x
1
2
3
|
squares
=
map
(
lambda
x: x
*
x,
range
(
9
))
print
squares
# 输出 [0, 1, 4, 9, 16, 25, 36, 49, 64]
|
我们再来看看reduce怎么玩?(下面的lambda表达式中有两个参数,也就是说每次从列表中取两个值,计算结果后把这个值再放回去,下面的表达式相当于:((((1+2)+3)+4)+5) )
1
2
|
print
reduce
(
lambda
x, y: x
+
y, [
1
,
2
,
3
,
4
,
5
])
# 输出 15
|
Python中的除了map和reduce外,还有一些别的如filter, find, all, any的函数做辅助(其它函数式的语言也有),可以让你的代码更简洁,更易读。 我们再来看一个比较复杂的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
num
=
[
2
,
-
5
,
9
,
7
,
-
2
,
5
,
3
,
1
,
0
,
-
3
,
8
]
positive_num_cnt
=
0
positive_num_sum
=
0
for
i
in
range
(
len
(num)):
if
num[i] >
0
:
positive_num_cnt
+
=
1
positive_num_sum
+
=
num[i]
if
positive_num_cnt >
0
:
average
=
positive_num_sum
/
positive_num_cnt
print
average
# 输出 5
|
如果用函数式编程,这个例子可以写成这样:
1
2
|
positive_num
=
filter
(
lambda
x: x>
0
, num)
average
=
reduce
(
lambda
x,y: x
+
y, positive_num)
/
len
( positive_num )
|
C++11玩的法:
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <iostream>
#include <algorithm>
#include <numeric>
#include <string>
#include <vector>
using
namespace
std;
vector num {2, -5, 9, 7, -2, 5, 3, 1, 0, -3, 8};
vector p_num;
copy_if(num.begin(), num.end(), back_inserter(p_num), [](
int
i){
return
(i>0);} );
int
average = accumulate(p_num.begin(), p_num.end(), 0) / p_num.size();
cout <<
"averge: "
<< average << endl;
|
我们可以看到,函数式编程有如下好处:
1)代码更简单了。
2)数据集,操作,返回值都放到了一起。
3)你在读代码的时候,没有了循环体,于是就可以少了些临时变量,以及变量倒来倒去逻辑。
4)你的代码变成了在描述你要干什么,而不是怎么去干。
最后,我们来看一下Map/Reduce这样的函数是怎么来实现的(下面是Javascript代码)
1
2
3
4
5
6
7
|
var
map =
function
(mappingFunction, list) {
var
result = [];
forEach(list,
function
(item) {
result.push(mappingFunction(item));
});
return
result;
};
|