AstralWind 的博客上有对函数式编程很好地讲解,一下做一些节选
1.1. 什么是函数式编程?
函数式编程使用一系列的函数解决问题。函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态。任何情况下,使用相同的参数调用函数始终能产生同样的结果。
在一个函数式的程序中,输入的数据“流过”一系列的函数,每一个函数根据它的输入产生输出。函数式风格避免编写有“边界效应”(side effects)的函数:修改内部状态,或者是其他无法反应在输出上的变化。完全没有边界效应的函数被称为“纯函数式的”(purely functional)。避免边界效应意味着不使用在程序运行时可变的数据结构,输出只依赖于输入。
可以认为函数式编程刚好站在了面向对象编程的对立面。对象通常包含内部状态(字段),和许多能修改这些状态的函数,程序则由不断修改状态构成;函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作。但这并不是说无法同时使用函数式编程和面向对象编程,事实上,复杂的系统一般会采用面向对象技术建模,但混合使用函数式风格还能让你额外享受函数式风格的优点。
1.2. 为什么使用函数式编程?
函数式的风格通常被认为有如下优点:
- 逻辑可证
- 模块化 函数式编程推崇简单原则,一个函数只做一件事情,将大的功能拆分成尽可能小的模块。小的函数更易于阅读和检查错误。
- 组件化 小的函数更容易加以组合形成新的功能。
- 易于调试 细化的、定义清晰的函数使得调试更加简单。当程序不正常运行时,每一个函数都是检查数据是否正确的接口,能更快速地排除没有问题的代码,定位到出现问题的地方。
- 易于测试 不依赖于系统状态的函数无须在测试前构造测试桩,使得编写单元测试更加容易。
- 更高的生产率 函数式编程产生的代码比其他技术更少(往往是其他技术的一半左右),并且更容易阅读和维护。
1.3. 如何辨认函数式风格?
支持函数式编程的语言通常具有如下特征,大量使用这些特征的代码即可被认为是函数式的:
- 函数是一等公民 函数能作为参数传递,或者是作为返回值返回。
- 匿名函数(lambda) lambda表达式定义一个匿名的函数,如果这个函数仅在编码的位置使用到,你可以现场定义、直接使用:
lst.sort(lambda o1, o2: o1.compareTo(o2))
- 封装控制结构的内置模板函数
在Python里,过滤由一个名为filter的内置函数实现。有了这个函数,解释器就学会了如何“过滤”,而我们只需要把规则告诉它:
lst2 = filter(lambda n: n > 0, lst)
这个函数带来的好处不仅仅是少写了几行代码这么简单。
封装控制结构后,代码中就只需要描述功能而不是做法,这样的代码更清晰,更可读。因为避开了控制结构的干扰,第二段代码显然能让你更容易了解它的意图。
另外,因为避开了索引,使得代码中不太可能触发下标越界这种异常,除非你手动制造一个。
函数式编程语言通常封装了数个类似“过滤”这样的常见动作作为模板函数。唯一的缺点是这些函数需要少量的学习成本,但这绝对不能掩盖使用它们带来的好处。
部分内建函数介绍
1.reduce(function, iterable[, initializer])
1 def reduce_(function, lst, initial): 2 result = initial 3 for num in lst: 4 result = function(result, num) 5 return result
它的第二个参数可以是任何可迭代的对象(实现了__iter__()方法的对象);
如果不指定第三个参数,则第一次调用function将使用iterable的前两个元素作为参数。
由reduce和一些常见的function组合成了下面列出来的内置函数:
all(iterable) == reduce(lambda x, y: bool(x and y), iterable) any(iterable) == reduce(lambda x, y: bool(x or y), iterable) max(iterable[, args...][, key]) == reduce(lambda x, y: x if key(x) > key(y) else y, iterable_and_args) min(iterable[, args...][, key]) == reduce(lambda x, y: x if key(x) < key(y) else y, iterable_and_args) sum(iterable[, start]) == reduce(lambda x, y: x + y, iterable, start)
2.map(function, iterable, ...)
def map_(function, lst): result = [] for item in lst: result.append(function(item)) return result
map还可以接受多个iterable作为参数,在第n次调用function时,将使用iterable1[n], iterable2[n], ...作为参数。
3.filter(function, iterable)
这个函数的功能是过滤出iterable中所有以元素自身作为参数调用function时返回True或bool(返回值)为True的元素并以列表返回
4.zip(iterable1, iterable2, ...)
这个函数返回一个列表,每个元素都是一个元组,包含(iterable1[n], iterable2[n], ...)。
例如:zip([1, 2], [3, 4]) --> [(1, 3), (2, 4)]
如果参数的长度不一致,将在最短的序列结束时结束;如果不提供参数,将返回空列表。
- 闭包(closure)
闭包是绑定了外部作用域的变量(但不是全局变量)的函数。大部分情况下外部作用域指的是外部函数。
闭包包含了自身函数体和所需外部函数中的“变量名的引用”。引用变量名意味着绑定的是变量名,而不是变量实际指向的对象;如果给变量重新赋值,闭包中能访问到的将是新的值。
闭包使函数更加灵活和强大。即使程序运行至离开外部函数,如果闭包仍然可见,则被绑定的变量仍然有效;每次运行至外部函数,都会重新创建闭包,绑定的变量是不同的,不需要担心在旧的闭包中绑定的变量会被新的值覆盖。