当我们谈论函数式编程时,我们在谈论什么?
map, flatmap, closure,filter, reduce, functor, monoid, monad, currying, lambda, cps, predicate, y-combinator…
推荐一本关于编程范式的书,郑晖的《冒号课堂:编程范式与OOP思想》,电子工业出版社。
- 编程范式是什么,三大核心编程范式有哪些?
- 第四代、第五代编程语言有什么特点?第三代语言的演变趋势是什么?
- 命令式编程和过程式编程有什么联系?结构化编程是什么?
- 声明式编程和函数式编程有什么关系?
- 什么是逻辑式编程,应用在哪些领域?
- 面向对象编程和函数式编程是对立的吗?
编程范式
编程范式的地位
编程范式的地位经常受到忽视。在IT世界中,数据结构和算法、操作系统、网络、数据库、分布式系统、架构与设计无疑具有非常重要的地位。要在这些领域有所成就,离不开大量的编程实践。而在编程实践活动中,程序员总是自觉或不自觉地采用某种世界观和方法论,这种世界观和方法论实际就是编程范式。
编程范式是编程的心法,体现了一个人的思维方式;不同编程范式在不同语言中体现为编程风格的不同。
编程范式的定义
范式译自因为的 paradigm
, 也译作“典范”、“范型”、“范例”。
可以说,编程范式是构建虚拟世界的世界观和方法论。
编程范式和编程语言
编程范式和编程语言具有如下关系:
- 抽象和具体。编程范式是抽象的,必须在编程语言中才能体现。
- 多对多对应关系:一种范式可以见于多种语言,一种语言也可以支持多种范式
- 主导范式:一种语言通常具有一种主导范式,由此形成这门语言的风格特征
编程语言
迄今为止,编程语言的演变大致经历了5代:
- 机器语言
- 汇编语言
- 高级语言
- 面向特定领域问题的语言
- 人工智能语言
目前主流的编程语言集中在第三、四、五代。
第三代语言是目前世界上最主要、最流行的编程语言,我们见到的大多数高级语言都是第三代;
第四代语言专注于业务逻辑和问题领域,不是通用性的,不满足图灵完备.
第五代语言在保持了第三代语言的通用性,此外一个突出的特点是面向人工智能领域。第四代和第五代语言有很多共同点,强调目标而不是过程,强调描述而不是实现。
实际上,第三代语言也在朝着这个方向进化。近年来很多第三代语言的新版本中增加了很多支持声明式、函数式的特性。
下面举几个例子说明一下目标和过程的区别。
例一
SQL语言属于第四代语言,注重目标,而不是过程。如:
|
|
使用SQL语言的人只需要声明自己想要的结果即可,而不必关心获取数据的具体过程。
例二
这个例子来源于《Java 8 函数式编程》,Richard Warburton著。
使用 Java 实现在 windows 控制台调用 help, 代码如下:
|
|
这段代码体现了 Java 语言设计中根深蒂固的命令式思想。代码编写者不需要关心如此多的细节。
作为对比,使用 Groovy 实现如下:
|
|
接下来,我们分别了解一下主要的编程范式:命令式、声明式、逻辑式。
命令式编程
命令式编程是最常见、最传统、最普及的编程范式。
为什么?因为绝大多数高级语言都是汇编语言的升级,汇编语言是从机器语言发展而来的,而机器语言大多是在冯诺依曼机发展起来的。冯诺依曼机是基于命令式的:从机器内存中依序获取指令和数据并执行。所以说,命令式编程本质上是冯诺依曼机器的运行机制的抽象。
命令式编程的核心观点是:程序是由若干指令组成的有序列表。
命令式编程的方法论:用变量存储数据,用语句执行命令。
那么,有没有非命令式的机器语言?理论上是有的。只能存在于非冯诺依曼机上,如数据流机、归约机。
过程式编程
过程式编程是指引入了procedure、function和subprogram的命令式编程。由于大多数命令式语言都具有过程式的特征,所以这两个概念实际具有密切的联系,某些场合可以不加区分。
结构化编程
结构化编程(Structured Programming)是一种编程原则,是在过程式基础上发展起来的。
结构化定理具有非常重要的意义。具备计算机编程基础的人对此都不会感到陌生。
结构化编程还有一个 SESE 原则,即Single Entry, Single Exit. 顺序、选择、循环每个基本结构都要满足这个单入口、单出口的原则。(这个原则实际上是模拟了电路设计。)
声明式编程
声明式编程和命令式编程是相对的。
声明式编程由若干规范(specification)的声明组成,即一系列陈述句,强调做什么,而非怎么做。
声明式编程起源于人工智能的研究。现在人工智能成为热点话题,实际上人工智能的研究早在几十年前就起步了。
声明式编程主要包括函数式编程和逻辑式编程。
函数式编程和逻辑式编程都具有悠久的历史,但以前多用于学术研究而非商业应用。
逻辑式编程
函数式编程在后文会单独讲解。在此之前先了解下逻辑式编程。
逻辑式编程的代表性语言是Prolog. Prolog,是Programming in Logic的缩写,是一种以一阶谓词为基础的逻辑性语言,是人工智能通用程序设计语言。
Prolog的三大核心:
- 以一阶谓词逻辑为基础的Horn子句集为语法
- 以Robinson的消解原理为工具
- 深度优先的控制策略
关于逻辑式、Prolog和人工智能,涉及到很深刻的理论知识,需要深厚的科学和数学背景才能理解。这里只是简单列一下概念,当做科普。
声明式编程和命令式编程的比较
区别:
- 命令式编程是**行动导向(Action-Oriented)的, 声明式编程是目标驱动(Goal-Driven)**的
- 命令式算法是显性的,目标是隐性的;声明式算法是隐性的,目标是显性的
- 命令式编程模拟电脑,声明式编程模拟人脑
联系:
- 从编程语言上看,这两种范式相互影响、相互渗透;近年来偏向命令式的语言逐渐开始支持声明特性
下面通过一个简单的 Python 的例子,演示命令式和声明式的特点。
例:
在1,2,3,4中取任意三个数字,列出所有排列。
使用REPL:
|
|
这段代码具有鲜明的命令式风格:使用事先声明的result保存结果,使用嵌套for循环精确控制细节。
使用声明式风格如何实现?只需一句话即可:
|
|
BTW, 实际就这个例子本身而言,完全可以采用已有的函数,因为这是一个数学问题。
|
|
三大核心编程范式
至此,我们已经了解了命令式、函数式和逻辑式,这三种编程范式被称为三大核心编程范式。
总的来讲,编程范式除了命令式就是声明式。
三大核心编程范式的比较:
范式 | 程序 | 输入 | 输出 | 程序设计 | 程序运行 |
---|---|---|---|---|---|
命令式 | 自动状态机 | 初始状态 | 最终状态 | 设计指令 | 命令执行 |
函数式 | 数学函数 | 自变量 | 因变量 | 设计函数 | 表达式转换 |
逻辑式 | 逻辑证明 | 题设 | 结论 | 设计命题 | 逻辑推理 |
面向对象
OOP与其他编程范式
不知识是否留意到,上面的图中 OOP 哪里去了?
答案是 OOP 和它们不在一个范畴,简单讲,没什么关系。严格讲,是正交的关系。
纯粹的OOP是不存在的,必须结合其他范式而存在。OOP虽然是从命令式发展而来,但这只是历史事实(可能带有某种历史上的必然性),但OOP的思想并不是跟命令式绑定的,也可以应用到函数式编程。
支持OOP的语言大多是命令式,但也存在函数式和逻辑式的OO语言。
OOP与三大核心编程范式正交,并且越来越广泛地向它们渗透。
面向 vs 导向
Oriented意思是“以…为方向,以…为目的”。由于着眼点和思维方式不同,不同编程范式各有侧重,一些编程范式使用oriented来表示一种倾向。
在大陆,Object-Oriented一般译作“面向对象”。这其实是有问题的。“面向”的宾语往往是已经确定的目标,“面向对象”使人误以为对象已经存在,这种译法让人莫名其妙,不知所谓。
在港台地区,Object-Oriented译作**“物件导向”**,且不说“物件”恰当与否,“导向”实际上比“面向”高很多。又如,经济学中“Market-Oriented”译作“市场导向”,而不是“面向市场”。
总之,“对象导向”更能反映这个范式本身的精髓——在设计中以对象为导向。
OOP的优点和局限性
OOP的思想无需多说。稍微有点计算机编程基础的人都不会陌生。它更接近人的思维方式,将函数按主体执行者分组,并与数据封装到一起,易于理解和使用。在传参方式上变得更加简洁。在面向对象流行之前,只有函数和参数;有了面向对象,原先作为参数的执行者在语法形式上可以放到点操作符的前面,如method(obj, arg)
变为obj.method(arg)
.
OOP是有局限性的。当初之所以流行是有商业利益推动。OOP有一段时间极其流行,近些年稍微冷却下来,人们对OOP的看法也趋于理性。归结起来,OOP流行的原因如下:
- 人们误以为OOP容易学习(实际在实践中很难把握其精髓)
- 人们以为OOP会使代码更容易复用(这是个错觉)
- OOP被一些商业公司过分宣传
- OOP创造了一个新兴软件产业
关于OOP的局限性,网上有不少资料。这里仅列举两个牛人说过的话:
有时,优雅的实现只需要一个函数。不是一个方法,不是一个类,不是一个框架,只是一个函数。
面向对象编程语言的问题在于,它总是附带着所有它需要的隐含环境。你想要一个香蕉,但得到的却是一个大猩猩拿着香蕉,而且还有整个丛林。