下图展示了一个常规的面向过程或面向对象风格编写的代码结构:
最显著的特征是,在代码执行的过程中,可以:
我们来做如下两步:
可以得出如下图所示的代码结构:
于是其中的函数,则变成了一个纯函数:确定的输入,一定产出相同的、确定性的结果。
上述两个不同代码风格的示例,仅仅展示了单个函数的执行过程,其差别可能并不显著,我们将函数拆解成多个子函数的级联调用。
从上图中不难看出以下几种问题:
如果我们按函数式的风格将上面的流程进行改造,则整个执行流程将变成:
在这个方式中,每一层的函数,都会把自己所有的依赖声明成函数的参数。同时,也把副作用从函数执行过程中剥离。但是这样做的一个后果是,从上到下的整个调用链路上,所有的依赖和参数,都要一层层地传递,中间层需要额外扮演中转站的角色。这显然并不是一个十分合理的设计。因为一但有新增的依赖或应用就会需要从上到下都要改动。同时违反了OCP和SRP。
当整个调用链上需要的额外依赖或配置项非常多样时,又不希望每次加入新的依赖都需要从头到尾加一遍,一个常见的解决方案是引入一个上下文对象,将代码执行过程中,所需要的所有相关依赖项,全部放在这个上下文对象中。如下图所示:
上下文的引入,只是缓解了这个问题,而没有从根本上解决掉,因为:
每一个函数调用,其实包含了如下三个层面的细节信息:
在程序中,这三件事情常常被放在一起,要么全做,要么全不做。上文所述,函数式编程中所提倡的延迟副作用及无副作用的要求,本质上仅仅是对何时执行及如何执行提出了些要求。如果我们能把事情的执行权出让给方法的调用方,自己依然保留决定权,其实就解决了这个问题。而且,执行所需要的细节,也不再需要做决定的函数来关心,让函数本身的逻辑更加抽象与通用。其基本形式如下图所示:
其中,函数2,依赖函数3,在之前的方案中,函数2负责执行函数3,同时负责传递结果。而在这个方案中,函数3的执行权,交给了上游的调用方,同时,自己也不不负责传递执行结果,也不负责准备函数3执行时所需要的信息。