javascript基础修炼(8)——指向FP世界的箭头函数 (2)

【函数式编程】,我们不再采用程序语言按照步骤来复现一个业务逻辑,而是换一个更为抽象的角度,用数学的眼光看待所发生的事情。那么上面的代码实际上所做的事情就是:

通过一系列变换操作,讲一个数据集x变成了数据集y

有没有一点似曾相识的感觉?没错,这就是我们熟知的【方程】,或者【映射】:
$$
y=f(x)
$$
我们将原来的代码换个样子,就更容易看出来了:

function prepare(){ return function (x){ return (new Task(x)).step(fn1).step(fn2).step(fn3); } } let f = prepare(); let y = f(x);

上面的例子中,通过高阶函数prepare( )将原来的函数改变为一个延迟执行的,等待接收一个参数x并启动一系列处理流程的新函数。再继续进行代码转换,再来看一下f(x)执行到即将结束时的暂态状况:

//fn2Result是XX.step(fn2)执行完后返回的结果(值和方法都包含在Task容器中) fn2Result.step(fn3);

上面的语句中,实际上变量只有fn2Result,step()方法和fn10都是提前定义好的,那么用函数化的思想来进行类比,这里也是实现了一个数据集x1到数据集y1的映射,所以它也可以被抽象为y = f ( x )的模式:

//先生成一个用于生成新函数的高阶函数,来实现局部调用 let goStep = function(fn){ return function(params){ let value = fn(params.value); return new Task(value); } } //fn2Result.step(fn3)这一句将被转换为如下形式 let requireFn2Result = goStep(fn3);

此处的requireFn2Result( )方法,只接受一个由前置步骤执行结束后得到的暂态结果,然后将其关键属性value传入fn3进行运算并传回一个支持继续链式调用的容器。我们来对代码进行一下转换:

function prepare(){ return function (x){ let fn2Result = (new Task(x)).step(fn1).step(fn2); return requireFn2Result(fn2Result); } }

同理继续来简化前置步骤:

//暂时先忽略函数声明的位置 let requireFn2Result = goStep(fn3); let requireFn1Result = goStep(fn2); let requireInitResult = goStep(fn1); function prepare(){ return function (x){ let InitResult = new Task(x); return requireFn2Result(requireFn1Result(requireInitResult(InitResult))); } }

既然已经这样了,索性再向前一步,把new Task(x)也函数化好了:

let createTask = function(x){ return new Task(x); }; 3.3 函数化的代码

或许你已经被上面的一系列转化弄得晕头转向,我们暂停一下,来看看函数化后的代码变成了什么样子:

function prepare(){ return function (x){ return requireFn2Result(requireFn1Result(requireInitResult(createTask(x)))); } } let f = prepare(); let y = f(x);

这样的编码模式将核心业务逻辑在空间上放在一起,而把具体的实现封装起来,让开发者更容易看到一个需求实现过程的全貌。

3.4 休息一下

不知道你是否有注意到,在中间环节的组装过程中,其实并没有任何真实的数据出现,我们只使用了暂态的抽象数据来帮助我们写出映射方法f的细节,而随后暂态的数据又被新的函数取代,逐级迭代,直到暂态数据最终指向了最外层函数的形参,你可以重新审视一下上面的推演过程来体会函数式编程带来的变化,这个点是非常重要的。

3.5 进一步抽象

3.3节中函数化的代码中,存在一个很长的嵌套调用,如果业务逻辑步骤过多,那么这行代码会变得很长,同时也很难阅读,我们需要通过一些手段将这些中间环节的函数展开为一种扁平化的写法。

/** *定义一个工具函数compose,接受两个函数作为参数,返回一个新函数 *新函数接受一个x作为入参,然后实现函数的迭代调用。 */ var compose = function (f, g) { return function (x) { return f(g(x)); } }; /** *升级版本的compose函数,接受一组函数,实现左侧函数包裹右侧函数的形态 */ let composeEx = function (...args) { return (x)=>args.reduceRight((pre,cur)=>cur(pre),x); }

看不懂的同学需要补补基础课了,需要注意的是工具函数返回的仍然是一个函数,我们使用上面的工具函数来重写一下3.3小节中的代码:

let pipeline = composeEx(requireFn2Result,requireFn1Result,requireInitResult,createTask); function prepare(){ return function (x){ return pipeline(x); } } let f = prepare(); let y = f(x);

还要继续?必须的,希望你还没有抓狂。代码中我们先执行prepare( )方法来得到一个新函数f,f执行时接收一个参数x,然后把x传入pipeline方法,并返回pipeline(x)。我们来进行一下对比:

//prepare执行后得到的新函数 let f = x => pipeline(x);

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfxpp.html