函数式编程和命令式编程

所谓命令式编程,是以命令为主的,给机器提供一条又一条的命令序列让其原封不动的执行。程序执行的效率取决于执行命令的数量。因此才会出现大O表示法等等表示时间空间复杂度的符号。

而函数式语言并不是通常意义上理解的“通过函数的变换进行编程”。注意到纯的函数式语言中是没有变量的(没有可以改变的东西,所有的东西在定义以后就都是不变的),那么这样的东西有什么好处呢?就比如,如果所有的东西都是不变的,那么我们又怎么进行编程呢?

实际上,我们在函数式编程中进行构建的是实体与实体之间的关系。在这种意义上,lisp虽然不是纯粹的函数式编程,但是也算是函数式编程一员。使用这种定义,大多数提供了原生的list支持的脚本语言也可以算混合了函数式语言的功能,但是这不是函数式语言的精髓。知其然,还要知其所以然。我们既然已经有了精确自然的命令式编程,又为什么还需要函数式编程呢?我们举个小例子。

int fab(int n) {
return n == 1 || n == 2 ? 1 : fab(n - 1) + fab(n - 2);
}

这是用C语言写的求斐波那契数列的第N项的程序,相应的Haskell代码是这样的:

fab :: (Num a) => a -> a
fab n = if n == 1 || n == 2 then 1 else fab(n - 1) + fab(n - 2)

看上去差不多对不对?但是这两个程序在执行的效率方面有着天差地别的差距。为什么呢?C语言是标准的命令式编程语言。因此对于你写下的每一行语句,C程序会原封不动地机械地去执行。如果想效率提高,你必须自己去分析程序,去人工地减少程序中执行的语句的数量。具体到这个C程序,我们注意到在每次函数调用时,都会产生两个新的函数调用。这时,实际产生的函数调用的数目是指数级别的!比方说,我们写fab(5),实际的执行结果是:

fab(5)
fab(4)
fab(3)
fab(2)
fab(1)
fab(2)
fab(3)
fab(2)
fab(1)

我们看到,fab(3)被求值了两遍。为了计算fab(5),我们实际执行了8次函数调用。

那么函数式语言呢?我们说过,函数式语言里面是没有变量的。换句话说,所有的东西都是不变的。因此在执行fab(5)的时候,过程是这样的:

fab(5)
fab(4)
fab(3)
fab(2)
fab(1)
fab(3)

总共只有五次应用。注意我说的是应用而不是调用。因为函数式语言里的函数本意并不是命令式语言里面的“调用”或者“执行子程序”的语义,而是“函数与函数之间的关系”的意思。比如fab函数中出现的两次fab的应用,实际上说明要计算fab函数,必须先计算后续的两个fab函数。这并不存在调用的过程。因为所有的计算都是静态的。haskell可以认为所有的fab都是已知的。因此实际上所有遇到的fab函数,haskell只是实际地计算一次,然后就缓存了结果。

本质上,这代表了我们提供给函数式语言的程序其实并不是一行一行的“命令”,而只是对数据变换的说明。这样函数式语言可以深入这些说明中,寻找这些说明中冗余的共性,从而进行优化。这就是函数式语言并不需要精心设计就会比命令式语言高效的秘密。命令式语言当然也可以进行这种优化,但是因为命令式语言是有边界效应的。而且大部分情况下都是利用边界效应进行计算,因此很难推广这种优化,只有少数几种窥孔优化能取得效果。

放到这个例子上,因为本质上我们两次的fab应用是重叠的。haskell发现了这个特点,于是将两次fab的结果缓存下来(注意,能缓存结果的必要条件是这个函数返回的值是不会变的!而这是函数式语言主要的特性)。如果后续的计算需要用到这两次fab的结果,就不需要再次重复计算,而只是直接提取结果就可以了。这就是上面几乎完全一样的两个程序效率相差如此之大的主要原因。

函数式语言有这样的优势,那么函数式语言有没有缺陷呢?当然是有的。函数式语言不如命令式语言那么纯粹。和机器一一对应,这在某些情形下会导致更差的效率和更低的开发效率。对计算模型不断深入的了解会缩短这两者之间的差距。然而,一定要注意命令式语言是植根于冯·诺依曼体系的,一旦新的体系产生了革命性的改变。那么命令式语言就不会再适用,而只能通过模拟的方法进行执行,到那个时候,函数式语言和命令式语言的地位就会完全颠倒过来,当然这并不是我们目前需要考虑的问题,但是在现在稍微了解一点函数式语言编程的思想是十分重要的。

时间: 2024-10-26 22:39:44

函数式编程和命令式编程的相关文章

Atitit 函数式编程与命令式编程的区别attilax总结  qbf

Atitit 函数式编程与命令式编程的区别attilax总结  qbf 1.1. 函数式程序就是一个表达式.命令式程序就是一个冯诺依曼机的指令序列. 命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机的指令序列. 而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式. 1.2. 面向对象语言中,数据类型分为两种--基本类型和对象类型(

函数式编程 vs 命令式编程

函数式编程 vs 命令式编程 函数式编程属于声明式编程(Declarative Programming),SQL就是典型的声明式编程. 函数式编程(Functional Programming) 关注行为 强调what,对应于数学里面的函数的对应法则. 像餐馆里吃饭,提出自己的要求即可 数学函数概念含有三个要素:定义域A.值域C和对应法则f.其中核心是对应法则f,它是函数关系的本质特征. 命令式编程(Imperative Programming) 关注数据 强调how,每一步都需要去做 像在家里

编程范式:命令式编程(Imperative)、声明式编程(Declarative)和函数式编程(Functional)

主要的编程范式有三种:命令式编程,声明式编程和函数式编程. 命令式编程:命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么.比如:如果你想在一个数字集合 collection(变量名) 中筛选大于 5 的数字,你需要这样告诉计算机:第一步,创建一个存储结果的集合变量 results:第二步,遍历这个数字集合 collection:第三步:一个一个地判断每个数字是不是大于 5,如果是就将这个数字添加到结果集合变量 results 中.代码实现如下: List<int>

命令式编程vs声明式编程

英文原文:Imperative vs Declarative 先统一一下概念,我们有两种编程方式:命令式和声明式. 我们可以像下面这样定义它们之间的不同: 命令式编程:命令"机器"如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现. 声明式编程:告诉"机器"你想要的是什么(what),让机器想出如何去做(how). 声明式编程和命令式编程的代码例子举个简单的例子,假设我们想让一个数组里的数值翻倍. 1.我们用命令式编程风格实现,像下面

声明式编程与命令式编程

先统一一下概念,我们有两种编程方式:命令式和声明式. 我们可以像下面这样定义它们之间的不同: ·命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现.·声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how). 声明式编程和命令式编程的代码例子: 举个简单的例子,假设我们想让一个数组里的数值翻倍. 我们用命令式编程风格实现,像下面这样: var numbers = [1,2,3,4,5] var doubled = [

python_way.day7 模块(configparser,xml,shutil,subprocess)、面向对象(上)(创建类,类的构成,函数式编程与面向对象编程的选择,类的继承)

python_way.day7 1.模块 configparser,xml,shutil,subprocess 2.面向对象(上) 创建类,类的构成,函数式编程与面向对象编程的选择,类的继承 1.模块 configparser 用于处理特定格式的文件,其本职上使用open来操作,只能是  [test1] 特定的格式 [test1] k1 = 123 k2 = True [test2] k1 = 123 k2 = v1 文件内容 1.获取 import configparser #打开文件找到文件

函数式编程与面向对象编程的比较

函数式编程作为结构化编程的一种,正在受到越来越多的重视.工程中不在只是面向对象编程,更多的人尝试着开始使用函数式编程来解决软件工程中遇到的问题. 什么是函数式编程?在维基百科中给出了详细的定义,函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象.函数编程语言最重要的基础是λ演算(lambda calculus).而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值

python编程:函数式编程和面向对象编程的对比

言而简之,面向对象编程就是把函数编程外面包装一个class类,然后再把这个class类指向一个对象 而class类中的函数在这里称为方法 举个例子: 用函数式编程写出一键发送邮件的脚本: def e_mail(email,message): print('邮件发送中...') return True e_mail('[email protected]','邮件内容') 用面向对象编程写出一键发送邮件的脚本: class Foo: #e_mail称为方法 def e_mail(self,email

Python - 命令式编程与符号编程

原文链接:https://zh.d2l.ai/chapter_computational-performance/hybridize.html本文是对原文内容的摘取和扩展. 命令式编程(imperative style programs) 使用编程语句改变程序状态,明确输入变量,并根据程序逻辑逐步运算. 易于理解:在Python里使用命令式编程时,大部分代码编写起来都很直观. 容易调试:可以很方便地进行单步跟踪,获取并分析所有中间变量,或者使用Python的调试工具. 虽然使用命令式编程很方便,