概论
在过去的近十年的时间里,面向对象编程大行其道。以至于在大学的教育里,老师也只会教给我们两种编程模型,面向过程和面向对象。孰不知,在面向对象产生之前,在面向对象思想产生之前,函数式编程已经有了数十年的历史。
诞生50多年之后,函数式编程(functional programming)开始获得越来越多的关注。不仅最古老的函数式语言Lisp重获青春,而且新的函数式语言层出不穷,比如Erlang、clojure、Scala、F#等等。目前最当红的Python、Ruby、Javascript,对函数式编程的支持都很强,就连老牌的面向对象的Java、面向过程的PHP,都忙不迭地加入对匿名函数的支持。越来越多的迹象表明,函数式编程已经不再是学术界的最爱,开始大踏步地在业界投入实用。
什么是函数式编程
Wiki上对Functional Programming的定义:In computer science,functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data.
简单地翻译一下,也就是说函数式编程是一种编程模型,他将计算机运算看做是数学中函数的计算,并且避免了状态以及变量的概念。
特点
1. 函数是"第一型"
所谓first class,指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。在函数式编程中,我们要做的是把函数传来传去,而这个,说成术语,我们把他叫做高阶函数。
在函数式编程中,函数是基本单位,是第一型,他几乎被用作一切,包括最简单的计算,甚至连变量都被计算所取代。在函数式编程中,变量只是一个名称,而不是一个存储单元,这是函数式编程与传统的命令式编程最典型的不同之处。
2. 只用"表达式",不用"语句"
"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操作,所以就被排斥在外。
当然,实际应用中,不做I/O是不可能的。因此,编程过程中,函数式编程只要求把I/O限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。
3. 没有"副作用"
所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
4. 不修改状态immutable data
上一点已经提到,函数式编程只是返回新的值,不修改系统变量。默认上变量是不可变的,如果你要改变变量,你需要把变量copy出去修改。
在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。递归是函数式编程的一个重要的概念,循环可以没有,但是递归对于函数式编程却是不可或缺的。
循环是在描述我们该如何地去解决问题。递归是在描述这个问题的定义。
递归相比于循环,具有着更加良好的可读性。但是,我们也不能忽略,递归而产生的StackOverflow,那么怎么办?解决这个问题,函数式编程为我们抛出了答案,尾递归。什么是尾递归,用最通俗的话说:就是在最后一部单纯地去调用递归函数.尾递归的原理,其实尾递归就是不要保持当前递归函数的状态,而把需要保持的东西全部用参数给传到下一个函数里,这样就可以自动清空本次调用的栈空间。这样的话,占用的栈空间就是常数阶的了。这需要语言或编译器的支持。Python就不支持。
5. 引用透明
引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
f(x) = y ,那么这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。
有了前面的第三点和第四点,这点是很显然的。其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫"引用不透明",很不利于观察和理解程序的行为。
优势
1. 数学推理
2. 并行程序parallelization
在并行环境下,各个线程之间不需要同步或互斥。
3.lazy evaluation 惰性求值
表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值,也就是说,语句如x:=expression; (把一个表达式的结果赋值给一个变量)明显的调用这个表达式被计算并把结果放置到 x 中,但是先不管实际在 x 中的是什么,直到通过后面的表达式中到 x 的引用而有了对它的值的需求的时候,而后面表达式自身的求值也可以被延迟,最终为了生成让外界看到的某个符号而计算这个快速增长的依赖树。
4.determinism 确定性
例子
Calculate partially invalid string with operations: "28+32+++32++39"
from operator import add
expr = "28+32+++32++39"
print reduce(add, map(int, filter(bool, expr.split("+"))))
"28+32+++32++39"
["28","32","","","32","","39"]
["28","32","32","39"]
[28,32,32,39]
131
avoid loops: Recursion
>>> name = None
>>> while name is None:
... name = raw_input()
... if len(name) < 2:
... name = None
def get_name():
name = raw_input()
return name if len(name) >= 2 else get_name()
High-ordered function
def speak(topic):
print "My speach is " + topic
def timer(fn):
def inner(*args, **kwargs):
t = time()
fn(*args, **kwargs)
print "took {time}".format(time=time()-t)
return inner
speaker = timer(speak)
speaker("FP with Python")
Partial function application
def log(level, message):
print "[{level}]: {msg}".format(level=level, msg=message)
from functools import partial
debug = partial(log, "debug")
debug("Start doing something")
debug("Continue with something else")
debug("Finished. Profit?")
Currying
def fsum(f):
def apply(a, b):
return sum(map(f, range(a,b+1)))
return apply
log_sum = fsum(math.log)
square_sum = fsum(lambda x: x**2)
simple_sum = fsum(int) ## fsum(lambda x: x)
>>> fsum(lambda x: x*2)(1, 10)
110
>>> import functools
>>> fsum(functools.partial(operator.mul, 2))(1, 10)
110