函数式编程
编程范式
编程范式(Programming Paradigm)是一种编程 语言的风格或方法,它提供了解决问题的模型和组织代码的方法。不同的编程范式提供了不同的视角来看待计算、组织数据与函数。
Python 是一种多范式编程语言,主要支持以下三种:
-
面向过程编程(Procedural Programming):基于“指令”和“过程”的概念。它把程序视为一系列按顺序执行的步骤,像流水账一样处理数据。这是最直观的编程方式,我们之前演示的各种示例程序,大多采用了这种方式。
-
函数式编程(Functional Programming,FP):强调使用纯函数来处理数据,避免修改状态和使用可变数据。它更像数学中的公式推导,这是我们接下来要介绍的重点。在学习函数式编程前,强烈建议读者先阅读一段关于 Lambda Calculus 编程语言的介绍。Lambda Calculus 是函数式编程的数学源头,虽然语法极简,却完美体现了函数式编程的精髓。
-
面向对象编程(OOP):核心是将数据和操作数据的方法封装在一起。其主要目的是提升大规模程序的可维护性和可扩展性,我们将在后续章节专门介绍。
除了 Python 支持的这三种编程范式,在其它编程语言中还存在着五花八门,各式各样的编程范式。比如,一个比较有特色的: LabVIEW 编程语言所采用的面向数据流的编程范式。
函数式编程的特点
在函数式编程中,函数不再仅仅是代码的组织单元,而是一等公民(First-Class Citizen)。这意味着函数可以像整数、字符串一样:被赋值给变量、作为参数传递给其他函数,或者作为其他函数的返回值。
这种特性让代码的编写方式发生了根本性的变化:
- 声明式而非命令式: 面向过程编程侧重于“怎么做”(How),需要详细描述每一步的变量修改和循环控制;而函数式编程侧重于“做什么”(What),通过函数的组合来描述数据之间的映射关系。
- 无副作用与不可变性: 在纯粹的函数式编程中,强调不修改变量的状态。数据一旦创建就不能改变(Immutable)。如果需要修改数据,函数会返回一个新的数据副本,而不是直接修改原始变量。这就像数学公式 y = f(x),无论调用多少次,只要 x 不变,y 永远不变,且不会影响外部环境。
- 高阶函数: 由于函数可以作为参数传递,我们可以编写一种“操作函数的函数”,称为高阶函数。这让代码变得极度抽象和简洁。同样的逻辑,面向过程可能需要写十行循环代码,而函数式编程可能只需要一行函数组合。
仅从代码角度来看,面向过程编程与函数式编程的明显区别在于,面向过程编程十分依赖变量与循环等结构。变量数据的变化反映了程序状态的改变,即不同过程的切换。而循环、条件等结构则负责程序在不同过程之间的跳转。而纯粹的函数式编程是不需要变量的,也不必使用各种结构,程序中就只有函数之间的调用。数据被传入一个函数进行处理,处理结果也就是函数的返回值,会再被作为参数传递给其它函数去做进一步处理。
相比于面向过程编程,函数式编程更加抽象,可以让代码变得非常简洁 、干净。其代价是更不容易理解,尤其是对于还不熟悉函数式编程的程序员。同样的逻辑,在面向过程的程序中可能是顺序的几行代码,符合人的自然阅读习惯;而在函数式编程中,它会被表达成嵌套的多次函数调用,逻辑在多层函数包装下变得非常隐晦,不那么直观。编程时,是否使用函数式编程的方法,除了考虑逻辑本身,还需要考虑合作者是否能够容易地理解并写出同样风格的代码。
然而,进入到这一部分,才是 Pythora 星球居民们真正感兴趣的部分。Pythora 星球居民都有一些阴暗的恶趣味:偶尔会故意写一些运行正确,但读起来莫名其妙的代码,就是为了看别人在读程序时抓耳挠腮的样子。
尽管采用一种编程范式就足以解决所有编程问题,但是不同的范式之间不是相互排斥的,当前主流的编程语言都不会只采用一种编程范式。大多是偏重某一两种编程范式,同时也尽量吸取采纳其它编程范式的优点。我们在编写程序的时候也不必拘泥于某一种单一编程范式,在以面向过程为主的代码中插入一些典型的函数式编程方法,比如递归、匿名函数等也是非常普遍的用法。