<
>

JS轻量级函数式编程实现XDM二

2022-06-14 17:56:00 来源:易采站长站 作者:

目录
前言偏函数传参现状封装 partial运行机制拓展 partial柯里化阶段小结

前言

承接上一篇《XDM,JS如何函数式编程?看这就够了!(一)》,我们知道了函数式编程的几个基本概念。

这里作简要回顾:

    函数式编程目的是为了数据流更加明显,从而代码更具可读性;函数需要一个或多个输入(理想情况下只需一个!)和一个输出,输入输出是显式的代码将更好阅读;闭包是高阶函数的基础;警惕匿名函数;弃用>

    本篇将着重介绍第 2 点中函数的输入,它是 JS 轻量函数式编程的基础之基础,重要之重要!!!

    偏函数

    传参现状

    我们经常会写出这样的代码:

    function ajax(url,data,callback) {
        // ..
    }
    function getPerson(data,cb) {
        ajax( "http://some.api/person", data, cb );
    }
    

    ajax 函数有三个入参,在 getPerson 函数里调用,其中 url 已确定,data 和 cb 两个参数则等待传入。(因为很多时候参数都不是在当前能确定的,需要等待其它函数的操作后确定了再继续传入)

    但是我们的原则是:入参最理想的情况下只需一个!

    怎样优化,可以实现这一点呢?

    我们或许可以在外层再套一个函数来进一步确定传参,比如:

    function getCurrentUser(cb) {
        ...// 通过某些操作拿到 CURRENT_USER_ID
        getPerson( { user: CURRENT_USER_ID }, cb );
    }
    

    这样,data 参数也已经确定,cb 参数仍等待传入;函数 getCurrentUser 就只有一个入参了!

    数据的传递路线是:

    ajax(url,data,callback) => getPerson(data,cb) => getCurrentUser(cb)
    

    这样函数参数个数逐渐减少的过程就是偏应用。

    也可以说:getCurrentUser(cb) 是 getOrder(data,cb) 的偏函数,getOrder(data,cb) 是 ajax(url,data,cb) 函数的偏函数。

    设想下:

    如果一个函数是这样的:

    function receiveMultiParam(a,b,c,......,x,y,z){
        // ..
    }
    

    我们难道还要像上面那样手动指定外层函数进行逐层嵌套吗?

    显示我们不会这么做!

    封装>

    我们只需要封装一个 partial(..) 函数:

    function partial(fn,...presetArgs) {
        return function partiallyApplied(...laterArgs){
            return fn( ...presetArgs, ...laterArgs );
        };
    }
    

    它的基础逻辑是:

    var partial =
        (fn, ...presetArgs) =>
            (...laterArgs) =>
                fn( ...presetArgs, ...laterArgs );
    

    把函数作为入参!还记得我们之前所说:

    一个函数如果可以接受或返回一个甚至多个函数,它被叫做高阶函数。

    我们借用 partial() 来实现上述举例:

    var getPerson = partial( ajax, "http://some.api/person" );
    var getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } ); // 版本 1
    

    以下函数内部分析非常重要:

    运行机制

    getPerson()>

    var getPerson = function partiallyApplied(...laterArgs) {
        return ajax( "http://some.api/person", ...laterArgs );
    };
    

    getCurrentUser() 的内部运行机制是:

    var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs) {
        var getPerson = function innerPartiallyApplied(...innerLaterArgs){
            return ajax( "http://some.api/person", ...innerLaterArgs );
        };
        return getPerson( { user: CURRENT_USER_ID }, ...outerLaterArgs );
    }
    

    数据进行了传递:

    getCurrentUser(outerLaterArgs) => getPerson(innerLaterArgs) => ajax(...params)
    

    我们通过这样一层额外的函数包装层,实现了更加强大的数据传递,

    我们将需要减少参数输入的函数传入 partial()中作为第一个参数,剩下的是 presetArgs,当前已知几个,就可以写几个。还有不确定的入参 laterArgs,可以在确定后继续追加。

    像这样进行额外的高阶函数包装层,是函数式编程的精髓所在!

    “随着本系列的继续深入,我们将会把许多函数互相包装起来。记住,这就是函数式编程!” —— 《JavaScript 轻量级函数式编程》

    实际上,实现 getCurrentUser() 还可以这样写:

    // 版本 2
    var getCurrentUser = partial(
        ajax,
        "http://some.api/person",
        { user: CURRENT_USER_ID }
    );
    // 内部实现机制
    var getCurrentUser = function partiallyApplied(...laterArgs) {
        return ajax(
            "http://some.api/person",
            { user: CURRENT_USER_ID },
            ...laterArgs
        );
    };
    

    但是版本 1 因为重用了已经定义好的函数,所以它在表达上更清晰一些。它被认为更加贴合函数式编程精神!

    拓展>

    我们再看看 partial() 函数还可它用:

    function partial(fn,...presetArgs) {
        return function partiallyApplied(...laterArgs){
            return fn( ...presetArgs, ...laterArgs );
        };
    }
    

    比如:将数组 [1,2,3,4,5] 每项都加 3,通常我们会这么做:

    function add(x,y) {
        return x + y
    [1,2,3,4,5].map( function adder(val){
        return add( 3, val );
    } );
    // [4,5,6,7,8]
    

    借助 partial():

    [1,2,3,4,5].map( partial( add, 3 ) );
    // [4,5,6,7,8]
    

    add(..) 不能直接传入 map(..) 函数里,通过偏应用进行处理后则能传入;

    实际上,partial() 函数还可以有很多变体:

    回想我们之前调用 Ajax 函数的方式:ajax( url, data, cb )。如果要偏应用 cb 而稍后再指定 data 和 url 参数,我们应该怎么做呢?

    function reverseArgs(fn) {
        return function argsReversed(...args){
            return fn( ...args.reverse() );
        };
    }
    function partialRight( fn, ...presetArgs ) {
        return reverseArgs(
            partial( reverseArgs( fn ), ...presetArgs.reverse() )
        );
    }
    var cacheResult = partialRight( ajax, function onResult(obj){
        cache[obj.id] = obj;
    });
    // 处理后:
    cacheResult( "http://some.api/person", { user: CURRENT_USER_ID } );
    

    柯里化

    函数柯里化实际上是一种特殊的偏函数。

    我们用>

    var curriedAjax = curry( ajax );
    var personFetcher = curriedAjax( "http://some.api/person" );
    var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } );
    getCurrentUser( function foundUser(user){ /* .. */ } );
    

    柯里化函数:接收单一实参(实参个数:1)并返回另一个接收下一个实参的函数。

    它将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。

    实现:

    function curry(fn,arity = fn.length) {
        return (function nextCurried(prevArgs){
            return function curried(nextArg){
                var args = prevArgs.concat( [nextArg] );
                if (args.length &gt;= arity) {
                    return fn( ...args );
                }
                else {
                    return nextCurried( args );
                }
            };
        })( [] );
    }
    

    阶段小结

    我们为什么要如此着重去谈“偏函数”(partial(sum,1,2)(3))或“柯里化”(sum(1)(2)(3))呢?

    第一,是显而易见的,偏函数或柯里化,可以将“指定分离实参”的时机和地方独立开来;

    第二,更有重要意义的是,当函数只有一个形参时,我们能够比较容易地组合它们。这种单元函数,便于进行后续的组合函数;

    对函数进行包装,使其成为一个高阶函数是函数式编程的精髓!

    至此,有了“偏函数”这门武器大炮,我们将逐渐轰开>

    以上就是JS轻量级函数式编程实现XDM二的详细内容,更多关于JS轻量级函数式编程XDM的资料请关注易采站长站其它相关文章!

暂时禁止评论

微信扫一扫

易采站长站微信账号