为何咱们须要知道“函数式编程”?

说在前面

注意,本文所讨论的函数式编程,并不等同于函数式编程“语言”,而是这么一个思想和概念,相信看到最后你或许可以明白这句话。react

 

问题

首先是关于计算机领域须要知道的一些事情,那就是硬件。数据库

因为硬件发展已经快要到达物理极限了,也就是说摩尔定律已经慢慢开始失效,因为我并非硬件相关的专家,因此也没法肯定这是否是真的,但咱们假设这就是真的。编程

摩尔定律失效事后会带来什么影响呢?那就是咱们编写的程序再也没法像之前那样,只要等上18月仍是多久(这个不是重点),就可让咱们的处理能力或是性能提高一倍。缓存

因此,当硬件的发展慢慢放缓,而咱们业务规模增加超过单机极限后,惟一的办法就是将程序分布到不一样的计算机上面运行,经过多个计算机来水平提高咱们的处理能力。网络

 

状态的一致性

当咱们把程序放到多个计算机上运行,造成集群,其中多个节点若是同时访问、修改同一个状态,就会形成数据一致性问题,这也是分布式程序为何这么难写的缘由之一。多线程

一样的事情其实发生在咱们在编写多线程应用程序的时候,当咱们在使用命令式或者面向对象语言的时候,咱们能够直接对状态进行修改,好比有一个变量,咱们随时能够给他赋值。架构

固然若是在任意时刻,只有一个线程可以访问和修改这个变量,这就没有问题。并发

可是别忘了其余线程也可以访问这个变量,当多个线程同时读取、修改变量的时候,若是没有任何的保护措施(好比锁),那么就有可能会出现状态被错误的读取和计算,甚至是被覆盖。框架

因此:异步

  • 多线程访问的是变量。
  • 分布式集群访问的是数据库或者缓存。

有什么区别呢?除了存放的位置不同,基本上遇到的问题是同样的,那就是必需要协调和控制好,并发所带来的状态一致性问题(不管是变量、仍是数据库、缓存,我统称它们为状态)。

咱们的程序拥有对状态的彻底控制权,在任什么时候候任何地方都可以修改状态,能够想象,若是咱们没有对状态进行有效的管理的话,就很容易形成混乱,维护性大大下降。

其实就跟我们一开始学编程的时候喜欢使用全局变量同样,可是如今的问题更棘手。

若是说全局变量的影响是平面的,咱们只须要线性的去梳理修改这些状态代码的前后顺序就可以解决BUG的话。

那么加上并发竞争,这个影响就是3D的,排查BUG以及组织程序的复杂程度整整被提升了一个维度,由于空间与时间再也不是一一对应的关系了。

我以前写的好几篇文章,几乎都是跟分布式以及一致性相关的主题,如今本文又提到了这些问题,不少重复的内容我就不赘述了,有兴趣的能够翻翻我以前的文章。

关于硬件,我留下一个问题给有兴趣的小伙伴,那就是为何显卡的计算能力大大超过了CPU?

 

函数式编程当中的纯函数

难道就没有一个行之有效的办法来解决这些使人棘手的问题吗?

若是你常常阅读博客或者关注最新的技术文章和框架的话,你会发现,不少框架都开始慢慢支持以及完善从“同步”发展到“异步”的这个过程当中了。

什么是同步?

咱们天天正在编写的代码是符合人脑思惟顺序的,从上到下依次执行,好比x = 2,而后x = x * x,很容易就得出最后x == 4的结论。

其中x就是一个变量,可能储存在CPU的寄存器当中,也可能储存在堆内存中,但这不是重点,它们都在同一个计算机上。

同步就是依次执行,按照咱们所编写代码的顺序逐步执行完全部的代码,咱们利用分支判断以及循环语句来控制执行的线路,依赖的是以前被计算好的状态变量。

什么是异步?

同步的缺陷很明显,因为是严格按照前后顺序执行代码的,这也是咱们预期的方式。

可是一旦涉及到IO操做,好比文件、网络,整个程序的运行就会被阻塞,所以多线程能够帮助咱们,将阻塞的操做与当前的流程分离开来,等读取完后再去执行相关的操做。

咱们能够经过回调或者事件的方式来异步的进行处理,所谓异步,简单粗暴的理解就是咱们调用了一个函数,他不会立马获得结果,而同步就能够获得结果,哪怕时间再长,咱们也等(阻塞)。

能够想象异步增长了代码的复杂程度,由于原本同步是直接返回结果的,异步就须要咱们在另外一个处理单元等待唤醒而后继续操做。

另外,同步的代码只能在一个CPU当中执行,而多线程异步则能够利用计算机上面其余的CPU,使其并行执行提升效率。

 

想象一下若是咱们有一个函数,它不会修改任何状态,仅仅是对参数进行计算,而后返回计算结果,而后咱们将这个函数分布到不一样的计算机上去执行,是否是就能不受制于单台计算机天花板的影响了呢?

因为这个函数不会修改任何状态,不会有任何的反作用,因此它能够在任何地方执行而不须要依赖其余的条件,这种函数被称之为纯函数。

纯函数就像是一个能够被随时移动到不一样地方去执行的单元。

所以,纯函数就能够被当作一个异步等待唤醒的处理器,咱们不知道它会在何时被执行,但咱们能够放心,由于它不会致使反作用,也不须要依赖其余的前置条件。

你或许注意到了本文所讲的内容其实就是Actor模型,没错,你能够认为每个Actor就是一个个纯函数。

但不管如何,你是自由的,你能够在actor执行单元里面作任何事情,可是请记住纯函数不得引起任何的外部状态修改,这是原则也是根本,由于咱们不想要反作用。

因此在纯函数式编程“语言”里面,根本就没有赋值操做,不过是描述对输入进行处理,而后返回结果而已,这可以让咱们少犯一些错误。

如今,处理器咱们有了(纯函数),它能够被当作异步处理单元在任何计算机上面执行,那么状态呢?

 

命令式vs声明式

注意我并不会介绍函数式编程的全部特性,有关这方面的资料我相信已经存在了。

什么是命令式?什么又是声明式?

举个例子,还记得你为何用Spring吗?最基本的就是由于你须要依赖注入。

我只须要声明一个Bean他须要依赖哪些类型的实例,Spring会为咱们找到而且传入,这就是声明式,而若是你本身进行实例化操做,那么你须要去找到这些依赖,这就是命令式。

Don’t call us, we’ll call you.

 

因为纯函数不会修改状态,他只是简单的输入(参数)-> 计算 -> 输出(返回)IO单元,所以也就没有了赋值操做。

因为纯函数不会修改状态,他只是简单的输入(参数)-> 计算 -> 输出(返回)IO单元,所以也就不须要赋值操做(对外部状态进行修改)。

若是说传统编程是对状态进行操做(修改),那么函数式编程语言就刚好相反,它不会修改状态,它只是描述计算的过程。

所以,传统的编程对状态的修改是命令式的,函数式编程对状态的修改则是描述声明式的。

若是没法理解这句话,想一想Java8里面的Stream API以及Lambda表达式吧,分解事后的状态转换、过滤、收集以声明的方式进行调用,更加直观方便,并且能够并行执行而无需修改其他代码。

实现函数式编程的具体语言、虚拟机执行环境以及框架,将会负责状态的维护,以及编排分布你的纯函数,在某一个地方某一个时间被激活执行。

不要认为我所讲的只是函数式编程“语言”,实际上只要遵循相关的规则,使用框架也是同样的,重要的是这些规则概念背后所表明的意义。

就像那句话怎么说来着?

就算是使用C语言,咱们也可以进行面向对象编程。但若是不懂面向对象,就算使用Java、C++,那也跟使用C语言没有区别。

或许咱们会使用这些高级语言的功能特性,可是却没法理解为何须要这些特性,咱们只是按照语言的规范来实现咱们的需求。

本末倒置的一个后果就是,咱们依赖特定的编程语言而非依赖咱们的思惟以及经验。

Erlang、Lisp、Scala、Akka、Vert.x、Reactor、RxJava等等等等,无论是语言、框架或者工具库,都能帮助咱们减小进行异步编程所要的工做量,但你或许会问:这有什么用呢?为何咱们须要异步呢?

答案就是,咱们须要开发分布式应用程序,它们可以在不一样的计算机上面运行,造成集群以提供大规模的并发应用服务,但同时,咱们也须要彻底利用好每一台计算机上的资源。

所以,咱们须要将资源的控制交给这些框架,交给它们背后所支撑的理论与实践,这样咱们就可以站在巨人的肩膀上。

 

但函数式编程不是银弹

由于跨网络的优化以及状态的管理,所以跟业务场景相关的偏好设定仍然没法被忽略。

咱们须要根据咱们本身的状况来进行组织,只有这样才能最大化的避免因为当前计算机体系架构所固有的缺陷而引起的问题,以最小的代价换取利用资源。

函数式编程可以让咱们放弃一些权力,来换取规则下的和平,但它终究不过是一种工具,若是咱们可以在适当的场景利用好这个工具,就可以使咱们的工做更加有效。

采用任何技术都没法脱离对原始业务的洞悉,只有这样,咱们才可以构建出最佳匹配的应用程序服务。

脱离应用场景的使用,不只会使得后期维护成本上升,还会使得架构的演化遭遇巨大的挑战,除非你真的明白在作什么,不然咱们可能永远也没法获得有效的改善。

 

最后

若是咱们遵循函数式编程的一些规范约束,就可以减小一些错误,由于正是因为这些规范约束的存在,才使得咱们避免陷入泥潭而没法自拔。

愈来愈多的框架都开始支持以及完善异步编程,就像Spring 5所推出的WebFlux,使用Reactor(https://projectreactor.io)做为基础支撑,带来的就是全异步化的声明式编程范式。

再例如Vert.X(https://vertx.io)这个让人用的上瘾的强大工具,当你浏览了愈来愈多的新开源项目,或者是已经存在的开源项目开始慢慢过渡的转变趋势。

你会发现,不少知识概念都是通用且能够互相转换的,异步、分布式、非阻塞、事件驱动、反应式等等…

而这些就是面向将来的编程知识,不管使用何种语言、框架或者工具库。

相关文章
相关标签/搜索