为什么大发pk10大发pk10我 们 需要知道“函数式编程”?

说在前面

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

 

问题

首先是大发pk10关于 计算机领域需要知道的一些事情,那就是硬件。

由于硬件发展已经快要到达物理极限了,也就是说摩尔定律已经慢慢开始失效,由于大发pk10我 并不是硬件相关的专家,所以也无法确定这是不是真的,但大发pk10大发pk10我 们 假设这就是真的。

摩尔定律失效过后会带来什么影响呢?那就是大发pk10大发pk10我 们 编写的程序再也无法像以前那样,只要等上18月还是多久(这个不是重点),就可以让大发pk10大发pk10我 们 的处理能力或是性能提升一倍。

所以,当硬件的发展慢慢放缓,而大发pk10大发pk10我 们 业务规模增长超过单机极限后,唯一的办法就是将程序分布到不同的计算机上面运行,通过多个计算机来水平提升大发pk10大发pk10我 们 的处理能力。

 

状态的一致性

当大发pk10大发pk10我 们 把程序放到多个计算机上运行,形成集群,其中多个节点如果同时访问、修改同一个状态,就会造成数据一致性问题,这也是分布式程序为什么这么难写的原因之一。

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

当然如果在任意时刻,只有一个线程能够访问和修改这个变量,这就没有问题。

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

所以:

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

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

大发pk10大发pk10我 们 的程序拥有对状态的完全控制权,在任何时候任何地方都能够修改状态,可以想象,如果大发pk10大发pk10我 们 没有对状态进行有效的管理的话,就很容易造成混乱,维护性大大降低。

其实就跟咱们一开始学编程的时候喜欢使用全局变量一样,但是现在的问题更棘手。

如果说全局变量的影响是平面的,大发pk10大发pk10我 们 只需要线性的去梳理修改这些状态代码的先后顺序就能够解决BUG的话。

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

大发pk10我 之前写的好几篇文章,几乎都是跟分布式以及一致性相关的主题,现在本文又提到了这些问题,很多重复的内容大发pk10我 就不赘述了,有兴趣的可以翻翻大发pk10我 之前的文章。

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

 

函数式编程当中的纯函数

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

如果大发pk10你 经常阅读大发pk10倍投方案或者关注最新的大发pk10技术 文章和框架的话,大发pk10你 会发现,很多框架都开始慢慢支持以及完善从“同步”发展到“异步”的这个过程中了。

什么是同步?

大发pk10大发pk10我 们 每天正在编写的代码是符合人脑思维顺序的,从上到下依次执行,比如x = 2,然后x = x * x,很容易就得出最后x == 4的结论。

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

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

什么是异步?

同步的缺陷很明显,由于是严格按照先后顺序执行代码的,这也是大发pk10大发pk10我 们 预期的方式。

但是一旦涉及到IO操作,比如文件、网络,整个程序的运行就会被阻塞,因此多线程可以大发pk10帮助 大发pk10大发pk10我 们 ,将阻塞的操作与当前的流程分离开来,等读取完后再去执行相关的操作。

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

可以想象异步增加了代码的复杂程度,因为本来同步是直接返回结果的,异步就需要大发pk10大发pk10我 们 在另一个处理单元等待唤醒然后继续操作。

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

 

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

由于这个函数不会修改任何状态,不会有任何的副作用,所以它可以在任何地方执行而不需要依赖其他的条件,这种函数被称之为纯函数。

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

因此,纯函数就可以被当做一个异步等待唤醒的处理器,大发pk10大发pk10我 们 不知道它会在什么时候被执行,但大发pk10大发pk10我 们 可以放心,因为它不会导致副作用,也不需要依赖其他的前置条件。

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

但无论如何,大发pk10你 是自由的,大发pk10你 可以在actor执行单元里面做任何事情,但是请记住纯函数不得引发任何的外部状态修改,这是原则也是根本,因为大发pk10大发pk10我 们 不想要副作用。

所以在纯函数式编程“语言”里面,根本就没有赋值操作,不过是描述对输入进行处理,然后返回结果而已,这能够让大发pk10大发pk10我 们 少犯一些错误。

现在,处理器大发pk10大发pk10我 们 有了(纯函数),它可以被当做异步处理单元在任何计算机上面执行,那么状态呢?

 

命令式vs声明式

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

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

举个例子,还记得大发pk10你 为什么用Spring吗?最基本的就是因为大发pk10你 需要依赖注入。

大发pk10我 只需要声明一个Bean他需要依赖哪些类型的实例,Spring会为大发pk10大发pk10我 们 找到并且传入,这就是声明式,而如果大发pk10你 自己进行实例化操作,那么大发pk10你 需要去找到这些依赖,这就是命令式。

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

 

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

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

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

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

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

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

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

就像那句话怎么说来着?

就算是使用C语言,大发pk10大发pk10我 们 也能够进行面向对象编程。但如果不懂面向对象,就算使用Java、C++,那也跟使用C语言没有区别。

或许大发pk10大发pk10我 们 会使用这些高级语言的功能特性,但是却无法理解为什么需要这些特性,大发pk10大发pk10我 们 只是按照语言的规范来实现大发pk10大发pk10我 们 的需求。

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

Erlang、Lisp、Scala、Akka、Vert.x、Reactor、RxJava等等等等,不管是语言、框架或者大发pk10工具 库,都能大发pk10帮助 大发pk10大发pk10我 们 减少进行异步编程所要的工作量,但大发pk10你 或许会问:这有什么用呢?为什么大发pk10大发pk10我 们 需要异步呢?

答案就是,大发pk10大发pk10我 们 需要开发分布式应用程序,它们能够在不同的计算机上面运行,形成集群以提供大规模的并发应用大发pk10服务 ,但同时,大发pk10大发pk10我 们 也需要完全利用好每一台计算机上的资源。

因此,大发pk10大发pk10我 们 需要将资源的控制交给这些框架,交给它们背后所支撑的理论与实践,这样大发pk10大发pk10我 们 就能够站在巨人的肩膀上。

 

但函数式编程不是银弹

因为跨网络的大发pk10优化 以及状态的管理,因此跟业务场景相关的偏好设定仍然无法被忽略。

大发pk10大发pk10我 们 需要根据大发pk10大发pk10我 们 自己的情况来进行大发pk10组织 ,只有这样才能最大化的避免由于当前计算机体系架构所固有的缺陷而引发的问题,以最小的代价换取利用资源。

函数式编程能够让大发pk10大发pk10我 们 放弃一些权力,来换取规则下的和平,但它终究不过是一种大发pk10工具 ,如果大发pk10大发pk10我 们 能够在适当的场景利用好这个大发pk10工具 ,就能够使大发pk10大发pk10我 们 的工作更加有效。

采用任何大发pk10技术 都无法脱离对原始业务的洞悉,只有这样,大发pk10大发pk10我 们 才能够构建出最佳匹配的应用程序大发pk10服务 。

脱离应用场景的使用,不仅会使得后期维护成本上升,还会使得架构的演化遭遇巨大的挑战,除非大发pk10你 真的明白在做什么,否则大发pk10大发pk10我 们 可能永远也无法得到有效的改善。

 

最后

如果大发pk10大发pk10我 们 遵循函数式编程的一些规范约束,就能够减少一些错误,因为正是由于这些规范约束的存在,才使得大发pk10大发pk10我 们 避免陷入泥潭而无法自拔。

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

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

大发pk10你 会发现,很多知识概念都是通用且可以互相转换的,异步、分布式、非阻塞、事件驱动、反应式等等…

而这些就是面向未来的编程知识,无论使用何种语言、框架或者大发pk10工具 库。

 

posted @ 2019-10-11 10:00 XingxueLiao 阅读(...) 评论(...) 编辑 收藏