函数式编程(一)

来源:互联网 发布:做淘宝淘客赚钱吗 编辑:程序博客网 时间:2024/05/18 05:52


源文档 http://docs.python.org/dev/howto/functional


在这份文档中,我们将介绍python函数式构建程序的特性。在介绍了函数式编程的概念后,我们将介绍其中的语言特性,比如iterator和generators以及相关的库模块itertools和functools。

介绍

这部节解释了函数式编程的基本概念;如果你只是对学习Python语言特性感兴趣,可以直接跳到下一节。

编程语言分解问题的方法有以下几种:

l  大多数编程语言是过程式的:程序由一系列指令组成,告诉计算机接受到程序输入后应该做什么。C,Pascal和Unix外壳程序是过程式语言。

声明性语言,你写了一份说明,描述了如何解决问题,然后语言系统计算出如何让计算机高效地运行。SQL可能是你比较熟悉的声明性语言;一个SQL query描述了你想要检索的数据集,SQL引擎可能选择浏览表格,也可能利用索引,此时subclauses会被首先运行。

面向对象语言操作对象集合。对象拥有内部状态而且支持检索或修改这些内部状态的方法。C++和Python是支持面向对象编程的语言,但不是一定要使用面向对象的特性。

函数式编程将问题分解为一系列函数。理想状况下,函数只接受输入,然后产生输出,不会有任何内在状态影响到应有的输出。著名的函数式语言包括ML系列(Standard ML,OCaml和其他变体)以及Haskell。

 

一些计算机语言的设计者选择某种特定的方式编程。这样会使采用其它方式编程变得困难。多范型(multi-paradigm)语言能支持多种编程方法。Lisp,C++和Python属于多范型;你能用这些语言写过程式、面向对象或者函数式的代码和库文件。在一个大型项目中,不同的部分可以用不同的方式,比如,GUI可能是面向对象的,而处理逻辑是过程式或函数式的。

在一个函数式程序中,输入经过一系列函数,每个函数接受输入并输出一些结果。函数式方法不鼓励函数存在某些副作用(side effects),包括修改内部状态以及其他无法在函数返回值中反映出来的修改。避免这些副作用要求不用那些会随着程序运行改变的数据结构;每个函数的输出必须只依赖其输入。

一些语言对其纯洁性要求很严格,甚至没有a=3和c = a + b这样的赋值语句。但要避免所有的副作用是困难的。比如打印到屏幕或写到磁盘上就是副作用。在Python中,对print()time.sleep()的调用都没有返回有用的值;它们只是调用它们的副作用:向屏幕发送一些文本信息或停止几秒运行。

用函数式方法编写的Python程序通常不需要严格地避免所有的I/O或赋值;相反地,它们提供函数形式的接口,但同时会在内部使用非函数的特性。比如,一个函数的功能是给本地变量赋值,但不会修改全局变量或产生其它副作用。

函数式编程可以看做是面向对象编程的对立面。对象是包含一些内部状态和方法的小单元,这些办法可以改变其内部状态。函数式编程希望尽可能避免状态改变,只希望通过函数间的数据流进行运作。在Python里你应该编写综合这两种处理的函数,接受并返回代表对象的实例(e-mail的写成,传输等)。

函数式编程仿佛是在一个奇怪的约束下工作。为什么需要避免对象和副作用呢?以下是函数式编程在理论和实践上的优势:

l  正式的可证明性

l  模块性

l  可组合性

l  调试和测试的便利性

正式的可证明性

很容易从数学上证明,函数式编程是正确的,这就是其在理论上的优势。

很长一段时间来研究者对编程的数学证明都很感兴趣。这跟验证程序在接受到各种输入时是否能有正确输出,以及阅读程序源代码并得出代码正确的结论是不同的;需要的目标是能有个严格的证明来保证对于任何输入程序都能得到正确的结果。

经常被用来验证程序正确性的技术是先写下程序里的不变量、输入数据和程序变量的理想属性。对每一行代码,先观察不变量XY在代码运行前是否正确,然后观察代码运行后有轻微变化的不变量X’Y’是否正确。持续这种过程直到程序结束。最后不变量应该符合预想状态下的程序输出。

赋值语句在函数式编程中不出现是因为难以处理这种技术;赋值语句会破坏原本正确的不变量,并且没有产生能用于运行的新的不变量。

不幸的是,正面程序的正确性是相当不切实际的,而且跟Python软件无关。那些不重要的程序也需要好几页的正面;对稍微复杂的程序的证明需要非常多的工作量。你日常生活使用的很少甚至没有软件(Python interpteter, XML parser, web browser)可以被证明是正确的。即使你写下了证明,也许里面会有错误,但你错误地认为你正面了它。

 

模块性

函数式编程的一个根据具有实际意义的优势是,它迫使你将问题分解成很多小片。程序最后变得更模块化。一个模块特点做一件事比一个大函数做复杂的处理要简单。小函数也更容易阅读和检查。

 

调试和测试的便利性

对函数式程序测试和调试是更加便利的。

因为函数通常是小块的而且被清楚地说明,所以调试被简化了。当一个程序不工作时,每个函数都是检验数据是否正确的交接点。你能看到输入和输出并马上找到出问题的函数。

调试也被简化了,因为每个函数都是潜在的单元测试对象。函数不依赖于系统状态,这些系统状态在运行测试前都需要被复制,很麻烦。你只要提供正确的输入,然后检查输出是否符合预期。

 

可组合性

当你编写函数式程序时,你将写很多拥有不同输入输出的函数。一些函数不可避免地用于专门的应用,另一些在很广泛的范围内是适用的。例如,一个函数接受目录路径然后返回目录下的所有XML文件,或者接受文件名并返回它的内容,这个函数能被运用到很多环境下。

久而久之你会形成自己的工具库。你会经常重新组织已有的函数并少量编写特定的函数来组成新的程序。