zoukankan      html  css  js  c++  java
  • JavaScript基础Curry化(021)

    时候我们希望函数可以分步接受参数,并在所有参数都到位后得到执行结果。为了实现这种机制,我们先了解函数在Javascript中的应用过程:

    1. 函数的“应用”(Function Application)

    在 函数化程序语言中,函数(Function)不是被调用(invoked)或被执行(called)的,而是被应用的(applied)。在 Javascript中,函数是一个对象,因此它也可以被“应用”: Function.prototype.apply()。下面就是一个例子:
    // define a function
    var sayHi = function (who) {
        return "Hello" + (who ? ", " + who : "") + "!";
    };
    // invoke a function
    sayHi(); // "Hello"
    sayHi('world'); // "Hello, world!"
    // apply a function
    sayHi.apply(null, ["hello"]); // "Hello, hello!"
    
    从 这个例子可以看出,调用一个函数的结果和应用一个函数的结果相同。函数的apply()方法接收两个参数,第一个参数是个对象,在函数的执行时期绑定到函 数声明内部的this对象引用;另一个参数是一个数组,用来传递函数的参数。如果第一个参数是null,那么函数在执行时,this就指向global对 象:这就是函数(不包括函数内的方法)被执行(invoked)时发生的情况。
    当函数内的方法被应用(applied)时,第一个参数就不能传null了,传递的应该是这个方法所属的那个对象:
    var alien = {
        sayHi: function (who) {
            return "Hello" + (who ? ", " + who : "") + "!";
        }
    };
    alien.sayHi('world'); // "Hello, world!"
    sayHi.apply(alien, ["humans"]); // "Hello, humans!"
    
    所以上面的例子里方法中的this引用方法所属的对象,而函数中的this,如果没有特别给定,引用的是global对象。
    通过上面的两个例子可以看出,JavaScript里的函数执行其实就是函数的应用过程。除了apply,函数还有另一个类似的固有方法call,当函数只有一个参数时,call的第二个参数可以不用传一个数组,而是直接给出那个唯一的参数:
    // the second is more efficient, saves an array
    sayHi.apply(alien, ["humans"]); // "Hello, humans!"
    sayHi.call(alien, "humans"); // "Hello, humans!"
    
     
    2. 函数的部分应用 (Partial Application)
    函数的执行过程既然是向它应用参数的过程,那我们是不是可以分步骤应用函数的参数呢?比如,我们有个add(x,y)的函数,它进行加法计算,是不是可以先应用一个参数,再应用下一个呢(注意,下面的代码并不合法,仅作为演示):
    // for illustration purposes
    // not valid JavaScript
    // we have this function
    function add(x, y) {
        return x + y;
    }
    // and we know the arguments
    add(5, 4);
    // step 1 -- substitute one argument
    function add(5, y) {
        return 5 + y;
    }
    // step 2 -- substitute the other argument
    function add(5, 4) {
        return 5 + 4;
    }
    上面代码里的step1和step2不合法。但由于函数的执行过程就是它的应用过程,我们如果可以编程实现这一机制,比如这样:
    var add = function (x, y) {
        return x + y;
    };
    // full application
    add.apply(null, [5, 4]); // 9
    // partial application
    var newadd = add.partialApply(null, [5]);
    // applying an argument to the new function
    newadd.apply(null, [4]); // 9
    
    上面的代码中,partialApply返回一个新的函数,这个新的函数通过接受剩余的参数,完成加法。可惜的是Javascript里并不提供partialApply,所以我们必须自己实现它。
     
    3. Curry化(Currying)
     Curry在这里并不是咖喱的意思,而是以Haskell Curry命名的一种编程模式,它把一个常规函数转化成可分步执行的函数:
    // a curried add()
    // accepts partial list of arguments
    function add(x, y) {
        var oldx = x, oldy = y;
        if (typeof oldy === "undefined") { // partial
            return function (newy) {
                return oldx + newy;
            };
        }
        // full application
        return x + y;
    }
    // test
    typeof add(5); // "function"
    add(3)(4); // 7
    // create and store a new function
    var add2000 = add(2000);
    add2000(10); // 2010
    
    通过上面的代码,add就可以既接受一个参数,也可接受两个参数,当它接受两个参数时,就一次完成加法运算;当参数只有一个时,它返回一个利用第一个参数完成加法的新函数,以便在后续的执行过程中继续加法运算。上面的代码可以通过去掉oldx和oldy来进一步精减:
    // a curried add
    // accepts partial list of arguments
    function add(x, y) {
        if (typeof y === "undefined") { // partial
            return function (y) {
                return x + y;
            };
        }
        // full application
        return x + y;
    }
    
    这样可以分步执行的add函数就实现了。类似地,使用函数的apply方法,可以做出通用的分步执行函数:
    function schonfinkelize(fn) {
        var slice = Array.prototype.slice,
            stored_args = slice.call(arguments, 1);
        return function () {
            var new_args = slice.call(arguments),
                args = stored_args.concat(new_args);
            return fn.apply(null, args);
        };
    }
    
    上面schonfinkelize 通过Array的slice方法,把arguments除了第1元素外(第1个元素是被curry的函数名)的其它所有元素都保存在 stored_args里,然后通过返回一个新的函数,并把之前保存的部分参数连接起来,完成函数分部执行的功能:
    // a normal function
    function add(x, y) {
        return x + y;
    }
    // curry a function to get a new function
    var newadd = schonfinkelize(add, 5);
    newadd(4); // 9
    // another option -- call the new function directly
    schonfinkelize(add, 6)(7); // 13
    也可多次执行schonfinkelize,让函数多次分步执行:
    // a normal function
    function add(a, b, c, d, e) {
        return a + b + c + d + e;
    }
    // works with any number of arguments
    schonfinkelize(add, 1, 2, 3)(5, 5); // 16
    // two-step currying
    var addOne = schonfinkelize(add, 1);
    addOne(10, 10, 10, 10); // 41
    var addSix = schonfinkelize(addOne, 2, 3);
    addSix(5, 5); // 16
    
  • 相关阅读:
    对象与内存控制1---实例变量和类变量
    数组与内存控制2--数组使用
    数组与内存控制1--数组初始化
    Java 三大特征之--多态
    简述Java面向对象三大特征:封装、继承、多态
    java程序初始化的顺序
    关于public static void main(String[] args)相关知识
    Java的优点
    前端面试攻略3------HTML和CSS部分
    前端面试攻略2------计算机网络部分
  • 原文地址:https://www.cnblogs.com/Bryran/p/3976131.html
Copyright © 2011-2022 走看看