什么是函数式编程?
函数式编程使用一系列的函数解决问题。函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态。任何情况下,使用相同的参数调用函数始终能产生同样的结果。
在一个函数式的程序中,输入的数据“流过”一系列的函数,每一个函数根据它的输入产生输出。函数式风格避免编写有“边界效应”(sideeffects)的函数:修改内部状态,或者是其他无法反应在输出上的变化。完全没有边界效应的函数被称为“纯函数式的”(purelyfunctional)。避免边界效应意味着不使用在程序运行时可变的数据结构,输出只依赖于输入。
可以认为函数式编程刚好站在了面向对象编程的对立面。对象通常包含内部状态(字段),和许多能修改这些状态的函数,程序则由不断修改状态构成;函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作。但这并不是说无法同时使用函数式编程和面向对象编程,事实上,复杂的系统一般会采用面向对象技术建模,但混合使用函数式风格还能让你额外享受函数式风格的优点。
所谓"第一等公民"(firstclass),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
例子:
定义一个函数
1
2
|
def add(x,y):
return x + y
|
1.1.使用函数赋值
使用def定义的函数也可以赋值,相当于为函数取了一个别名,并且可以使用这个别名调用函数:
1
2
|
add_a_number_to_another_one_by_using_plus_operator = add
print add_a_number_to_another_one_by_using_plus_operator( 1 , 2 )
|
1.2.作为参数
如果你对OOP的模板方法模式很熟悉,相信你能很快速地学会将函数当作参数传递。两者大体是一致的,只是在这里,我们传递的是函数本身而不再是实现了某个接口的对象
def
reduce_(function,lst, initial):
result
=
initial
for
num
in
lst:
result
=
function(result,num)
return
result
print
reduce_(add,lst,
0
)
现在,想要算出乘积,可以这样做:
print
reduce_(
lambda
x,y: x
*
y,lst,
1
)
1.3.作为返回值
将函数返回通常需要与闭包一起使用(即返回一个闭包)才能发挥威力。
1:lst
=
map_(
lambda
x:add(
10
,x), lst)
2:lst
=
map_(add_to(
10
),lst)
#add_to(10)返回一个函数,这个函数接受一个参数并加上10后返回
3:def
add_to(n):
return
lambda
x:add(n, x)
上面函数中
lambda
x:add(n, x)是一个闭包,它作为一个返回值,功能是结构一个参数并加上10,也就等同于1中的式子。
2、匿名函数(lambda)
lambda提供了快速编写简单函数的能力。对于偶尔为之的行为,lambda让你不再需要在编码时跳转到其他位置去编写函数。
lambda表达式定义一个匿名的函数,如果这个函数仅在编码的位置使用到,你可以现场定义、直接使用:
lst.sort(
lambda
o1,o2: o1.compareTo(o2))
关于匿名函数的使用场合问题:lambda的目的是为了编写偶尔为之的、简单的、可预见不会被修改的匿名函数。这种风格虽然看起来很酷,但并不是一个好主意,特别是当某一天需要对它进行扩充,再也无法用一个表达式写完时。如果一开始就需要给函数命名,应该始终使用def关键字。
3、封装控制结构的内置模板函数
为了避开边界效应,函数式风格尽量避免使用变量,而仅仅为了控制流程而定义的循环变量和流程中产生的临时变量无疑是最需要避免的。
假如我们需要对刚才的数集进行过滤得到所有的正数,使用指令式风格的代码应该像是这样:
lst2
=
list
()
for
i
in
range
(
len
(lst)):
#模拟经典for循环
if
lst[i]>
0
:
lst2.append(lst[i])
这段代码把从创建新列表、循环、取出元素、判断、添加至新列表的整个流程完整的展示了出来,俨然把解释器当成了需要手把手指导的傻瓜。然而,“过滤”这个动作是很常见的,为什么解释器不能掌握过滤的流程,而我们只需要告诉它过滤规则呢?
在Python里,过滤由一个名为filter的内置函数实现。有了这个函数,解释器就学会了如何“过滤”,而我们只需要把规则告诉它:
lst2
=
filter
(
lambda
n:n >
0
,lst)
这个函数带来的好处不仅仅是少写了几行代码这么简单。
闭包是绑定了外部作用域的变量(但不是全局变量)的函数。大部分情况下外部作用域指的是外部函数。
闭包包含了自身函数体和所需外部函数中的“变量名的引用”。引用变量名意味着绑定的是变量名,而不是变量实际指向的对象;如果给变量重新赋值,闭包中能访问到的将是新的值。
闭包使函数更加灵活和强大。即使程序运行至离开外部函数,如果闭包仍然可见,则被绑定的变量仍然有效;每次运行至外部函数,都会重新创建闭包,绑定的变量是不同的,不需要担心在旧的闭包中绑定的变量会被新的值覆盖。
闭包是一类特殊的函数。如果一个函数定义在另一个函数的作用域中,并且函数中引用了外部函数的局部变量,那么这个函数就是一个闭包。下面的代码定义了一个闭包:
def
f():
n
=
1
def
inner():
print
n
inner()
n
=
'x'
inner()
函数inner定义在f的作用域中,并且在inner中使用了f中的局部变量n,这就构成了一个闭包。闭包绑定了外部的变量,所以调用函数f的结果是打印1和'x'。这类似于普通的模块函数和模块中定义的全局变量的关系:修改外部变量能影响内部作用域中的值,而在内部作用域中定义同名变量则将遮蔽(隐藏)外部变量。
如果需要在函数中修改全局变量,可以使用关键字global修饰变量名。Python2.x中没有关键字为在闭包中修改外部变量提供支持,在3.x中,关键字nonlocal可以做到这一点:
#Python 3.x supports`nonlocal'
def
f():
n
=
1
def
inner():
nonlocaln
n
=
'x'
print
(n)
inner()
print
(n)
调用这个函数的结果是打印1和'x',如果你有一个Python3.x的解释器,可以试着运行一下。
由于使用了函数体外定义的变量,看起来闭包似乎违反了函数式风格的规则即不依赖外部状态。但是由于闭包绑定的是外部函数的局部变量,而一旦离开外部函数作用域,这些局部变量将无法再从外部访问;另外闭包还有一个重要的特性,每次执行至闭包定义处时都会构造一个新的闭包,这个特性使得旧的闭包绑定的变量不会随第二次调用外部函数而更改。所以闭包实际上不会被外部状态影响,完全符合函数式风格的要求。(这里有一个特例,Python3.x中,如果同一个作用域中定义了两个闭包,由于可以修改外部变量,他们可以相互影响。)
虽然闭包只有在作为参数和返回值时才能发挥它的真正威力,但闭包的支持仍然大大提升了生产率。
为了避开边界效应,不可变的数据结构是函数式编程中不可或缺的部分。不可变的数据结构保证数据的一致性,极大地降低了排查问题的难度。
函数式编程语言一般会提供数据结构的两种版本(可变和不可变),并推荐使用不可变的版本。
一个完整的命令式转向函数式的编程实例
写声明式代码,而不是命令式
下面的程序演示三辆车比赛。每次移动时间,每辆车可能移动或者不动。每次移动时间程序会打印到目前为止所有车的路径。五次后,比赛结束
参看:http://www.ruanyifeng.com/blog/2012/04/functional_programming.html