一.什么是函数式编程?
函数式编程思想来源于伟大数学家阿隆佐设计的lambda验算,是指用函数来解决与计算相关的几乎所有问题。与我们平时常见的指令式编程相对,也是一种典型的编程范式。举个例子。
需要计算的数学表达式为:
指令式的编程方式如下:
而函数式编程则会将每一个运算过程定义为不同的函数,编程方式如下:
从以上的对比例子可以看出,和面向对象编程以对象为模块的思想一样,函数式编程是以函数为核心来组织模块的,这种组织方式更有利于写出模块化的代码。
二.函数式编程的基本准则
与学习面向对象编程一样,函数式编程同样有几个鲜明的特点,需要我们在编程时牢记。
1.函数永远是“一等公民”
所谓一等公民是指,函数与我们平时所使用的其他数据类型地位一样:
(1)可以赋值给一个变量
(2)可以作为参数进行传递
(3)可以作为别的函数的返回值
将函数作为一等公民有利用代码的模块化,接下来我们来举个例子:
程序需要完成的目标:
函数不是一等公民的情况:
函数是一等公民的情况:
从以上例子可以看出,函数式编程的模块化程度更高,且代码量更少
2.尽量写“纯函数”
所谓纯函数是指,给定相同输入总能得到相同输出的函数。纯函数需要同时满足下面两个条件:
(1)函数的结果只依赖于输入的参数且与外部变量和环境无关——只要输入相同,返回值总是不变的。
(2)除了返回值外,不修改程序的外部状态(比如全局变量、入参)——FP中所有的变量都是final的,这样设计的原因是因为lambda验算只关心计算的结果而不关心每个状态的值。
同样,举一个 纯函数VS不纯函数 的例子:
不纯的:
纯的:
从上面不纯函数版本可以看出,checkAge函数的结果依赖于minimum这个外部变量,尽管可以带来便利,但是也会出现很多的副作用。
在数学领域函数的定义为,假设有两个变量x和y,如果对于任意一个x都有一个唯一确定的y与其对应,那么就称y是x的函数。从这个层面上来理解的话,所谓的纯函数就是数学函数。
三.函数式编程的优势
1.单元测试与debug都十分的容易
尽管函数式编程的条件要求比较苛刻,但是在后期的调试中它的体验感却好过指令式程序好几个level。因为FP程序中的错误不依赖于之前运行过的不相关的代码,如果一段FP程序没有按照预期设计那样运行,这些错误是百分之一百可以重现的。
在这种情况下,你只需要按图索骥的逐个检查返回值即可。而在一个指令式程序中,一个bug可能有时能重现而有些时候又不能。因为这些函数的运行依赖于某些外部状态, 而这些外部状态又需要由某些与这个bug完全不相关的代码通过某个特别的执行流程才能修改。
2.可并行执行
在函数式编程中,不需要进行任何改动,所有FP程序(即使是单线程)都可以并发执行。那是因为在函数式编程中没有采用锁机制,因此就不存在死锁或者并发竞争的问题。以下面的程序为例:
在指令式编程中,因为每一个函数都可能改变其状态其后面的函数会依赖于前一个函数,因此,往往是顺序执行。而在函数式编程中,编译器可对代码进行分析,从而分析出pureFunction1和pureFunction2哪个函数费时,从而安排他们并行执行。
3.热部署
所谓热部署是指可在不停机的状态下进行状态更新,目前大部分的软件都支持这一功能。
4.currying技术实现函数封装
currying技术将接收多个参数的函数变换为接收其中部分参数,并且返回接收余下参数的新函数。通俗的来讲就是,只向函数传递一部分参数来调用它,让它返回一个函数去处理剩下的参数。可以快速且简单的实现函数封装。为了便于理解还是举一个例子:
这里是自定义的函数add,它接收一个参数并返回一个新的函数,调用add之后返回的函数就会以闭包的方式记住add的第一个参数。
5.延迟求值
延迟求值是指表达式不在它被绑定到变量时就立即求值,而是在该值被用到的时候才计算求值。很显然,延迟求值的正确性需要纯函数的保证,即无论什么时候被执行,结果都不变。延迟求值在处理无穷数列时特别有效。下面举个例子:
对于斐波那契数列这样一个无穷数列,如何
如果不考虑具体的语言和实现,可以将问题拆解成几个函数。一个函数负责生成斐波那契数列,一个函数负责筛选数列中的偶数,再写个函数挑出任意数列中能被3整除的数。
将第一个函数的输出作为后面两个函数的输入,问题就得到解决了。
但问题是斐波那契数列是一个无穷数列,一般的语言无法输出一个无穷的数据结构。不过这对于支持延迟求值的语言来说不成什么问题,因为每个值只有在真正被用到的时候才会被计算出来,因此完全可以像这样定义一组无穷的斐波那契数列
然后完成上面三个需求只需要这样:
6.强制惰性语言顺序执行
在以上所讲的惰性语言中,因无法保证第一行在第二行之前执行,如果我们直接使用则无法处理IO请求。为了使得惰性语言可以顺序执行,我们引入了continuation。函数可以根据continuation将程序传递到指定位置,从而实现IO请求。
假如我们不想编译器打乱以下代码的执行顺序,保证其顺序执行,则我们就需要使用continuation技术。
在上例中,add多了一个参数:一个函数,add必须在完成自己的计算后,调用这个函数并把结果传给它。这时square就是add的一个continuation。上面两段程序中j的值都是225。
这样我们在理解下面的IO请求时就很容易理解了。
小结
以上就是函数式编程的一些概念知识点,为了对函数式编程有一个更加直观的体验,在接下来的日子里我会继续更新,敬请关注。
“我是一名从事了10年开发的高龄程序员,最近我花了一些时间整理了一个完整的学习C语言、C++的路线,项目源码和工具。对于想学习C/C++的小伙伴而言,学习的氛围和志同道合的伙伴很重要,笔者强烈推荐我主页的C语言/C++编程爱好者的聚集地!
欢迎初学和进阶中的小伙伴!工作需要、感兴趣、为了入行、转行需要学习C/C++的伙伴可以一起学习!”
喜欢小编的记得动动您的小指点个关注哟!最后分享学习路线图和资料给爱学习的小伙伴们~