js的currying函数应用与闭包

curring的概念将函数式编程的概念和默认参数以及可变参数结合在一起.一个带n个参数,curried的函数固化第一个参数为固定参数,并返回另一个带n-1个参数的函数对象,分别类似于LISP的原始函数car和cdr的行为。currying能泛化为偏函数应用(partial function application, PFA),p 这种函数将任意数量(顺序)的参数的函数转化为另一个带剩余参数的函数对象。

最早期的curry函数有点多态的意味,就是根据函数参数在内部选用分支:

// http://www.openlaszlo.org/pipermail/laszlo-user/2005-March/000350.html
// On 8 Mar 2005, at 00:06, Steve Albin wrote:
      function add(a, b) {
        if (arguments.length < 1) {
          return add;
        } else if (arguments.length < 2) {
          return function(c) { return a + c }
        } else {
          return a + b;
        }
      }
 
      var myadd = add( 2 );
      var total = myadd(3);

日本的一个先行者可能在未搞清arguments也能用Array的原生方法转换为数组的时候,用非常复杂的正则与eval搞出一个更接近现代currying意味的函数。

function curry(fun) {
  if (typeof fun != 'function') {
    throw new Error("The argument must be a function.");
  }
  if (fun.arity == 0) {
    throw new Error("The function must have more than one argument.");
  }
 
  var funText = fun.toString();
  var args = /function .*\((.*)\)(.*)/.exec(funText)[1].split(', ');
  var firstArg = args.shift();
  var restArgs = args.join(', ');
  var body = funText.replace(/function .*\(.*\) /, "");
 
  var curriedText =
    "function (" + firstArg + ") {" +
    "return function (" + restArgs + ")" + body +
    "}";
 
  eval("var curried =" + curriedText);
  return curried;
}

运行的代码

      function curry(fun) {
        if (typeof fun != 'function') {
          throw new Error("The argument must be a function.");
        }
        if (fun.arity == 0) {
          throw new Error("The function must have more than one argument.");
        }

        var funText = fun.toString();
        var args = /function .*\((.*)\)(.*)/.exec(funText)[1].split(', ');
        var firstArg = args.shift();
        var restArgs = args.join(', ');
        var body = funText.replace(/function .*\(.*\) /, "");

        var curriedText =
          "function (" + firstArg + ") {" +
          "return function (" + restArgs + ")" + body +
          "}";

        eval("var curried =" + curriedText);
        return curried;
      }

      function sum(x, y) {
        return x + y;
      }
      function mean3(a, b, c) {
        return (a + b + c)/3;
      }

      var a = curry(sum)(10)(15)
      alert(a)//25
      var b = curry(mean3)(10)(20, 30);
      alert(b)//20
      var c =  curry(curry(sum))(10)()(20);
      alert(c);
      var d = curry(curry(mean3)(10))(20)(30);
      alert(d);

接着是闭包的流行,与数组转换arguments的技术的发现,现代currying函数终于粉墨登场,就好像15~17世纪大航海时代的地理大发现,javascript的世界突然间开阔了许多。

//一个简单的现代currying函数
function curry (fn, scope) {
    var scope = scope || window;
    var args = [];
    for (var i=2, len = arguments.length; i < len; ++i) {
        args.push(arguments[i]);
    };
    return function() {
        fn.apply(scope, args);
    };
}

一般的currying函数只有两重,执行情况如下,第一次执行参数不足返回内部函数,第二次执行才最终完成。不过针对这参数,我们还是可以做一些文章。看如下函数:

function sum(){
    var result=0;
    for(var i=0, n=arguments.length; i<n; i++){
        result += arguments[i];
    }
    return result;
}
alert(sum(1,2,3,4,5)); // 15

这就没有所谓的参数不足问题,传入一个参数,它也计算。但不传入参数呢?无错,区别在于有没有参数。我们可以让它不断执行自身,如果参数存在的情况下。最后在没有参数的情况下,一次过执行。换言之,前面的步骤是用于储存参数。

var sum2= curry(sum);
    sum2= sum2(1)(2)(3)(4)(5);
 
sum2(); // 15

比起一般的currying函数,这有点难度。具体看注解:

var curry= function(fn){//原函数的参数为函数
  return function(args){//内部函数的参数为数组,由于立即执行,因此直接到第三重去
    //args是相对于第三重内部函数可是全局变量
    var self= arguments.callee;//把自身保存起来(就是那个数组为参数的第二重函数)
    return function(){ //这才是第二次调用的函数
      if(arguments.length){//如果还有要添加的参数
        [].push.apply(args,arguments);//apply把当前传入的所有参数放进args中
        return self(args);
      }else{
        return fn.apply(this,args);//apply的第二参数为数组
      }
    }
  }([]);
};

运行的代码:

      function sum(){
        var result=0;
        for(var i=0, n=arguments.length; i<n; i++){
          result += arguments[i];
        }
        return result;
      };
      var curry = function(fn){//原函数的参数为函数
        return function(args){//内部函数的参数为数组,由于立即执行,因此直接到第三重去
          var self= arguments.callee;//把自身保存起来
          return function(){ //这才是第二次调用的函数
            if(arguments.length){//如果还有要添加的参数
              [].push.apply(args,arguments);
              return self(args);
            }
            else return fn.apply(this,args);//执行
          }
        }([]);
      };

      var sum2= curry(sum);
      sum2= sum2(1)(2)(3)(4)(5);
      alert(sum2());

或者每次传入多个参数,运行的代码:

      function sum(){
        var result=0;
        for(var i=0, n=arguments.length; i<n; i++){
          result += arguments[i];
        }
        return result;
      };
      var curry = function(fn){//原函数的参数为函数
        return function(args){//内部函数的参数为数组,由于立即执行,因此直接到第三重去
          var self= arguments.callee;//把自身保存起来
          return function(){ //这才是第二次调用的函数
            if(arguments.length){//如果还有要添加的参数
              [].push.apply(args,arguments);
              return self(args);
            }
            else return fn.apply(this,args);//执行
          }
        }([]);
      };

      var sum2= curry(sum);
      sum2= sum2(1,2,3);
      sum2= sum2(4,5,6);
      sum2= sum2(7,8,9);
      alert(sum2());

但上面的函数有不足之处,最后怎么也要放个括号,我们想只要参数足够就返回结果,多出的参数忽略。改进如下:

function curry(f) {
   if (f.length == 0) return f;
   function iterate(args) {
     if (args.length <= f.length)
       return f.apply(null, args);
     return function () {
       return iterate(args.concat(Array.prototype.slice.call(arguments)));
     };
   }
   return iterate([]);
 }

运行的代码:

      function curry(f) {
        if (f.length == 0) return f;
        function iterate(args) {
          if (args.length >= f.length)
            return f.apply(null, args);
          return function () {
            return iterate(args.concat(Array.prototype.slice.call(arguments)));
          };
        }
        return iterate([]);
      }
      function mean3(a, b, c) { return (a + b + c) / 3; }
   
      var curriedMean3 = curry(mean3);
      alert(curriedMean3(1)(2, 3)); // => 2
      alert(curriedMean3(1)(2)(3));//空括号无效
      alert(curriedMean3()(1)()(2)()(3)); // => 2
      alert(curriedMean3(1, 2)(3, 4)); // => 2 (第四个参数无效)

推荐另外一篇讨论currying的 大师级别 文章《 实现函数式编程Javascript currying function

本文转自:https://www.cnblogs.com/rubylouvre/archive/2009/11/09/1598761.html

以上是js的currying函数应用与闭包的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>