• JavaScript中的Partial Application和Currying


      这篇文章是一篇学习笔记,记录我在JS学习中的一个知识点及我对它的理解,知识点和技巧本身并不是我原创的。(引用或参考到的文章来源在文末)

      先不解释Partial Application(偏函数应用)和Currying(加里化)的字面意思,从实际的示例入手会比较方便

    比如有个function sum(a,b){return a+b}

    如果我们想固定一个值,就要先设定这个值:
    var a=1;
    sum(a,1);  sum(a,100);….
    或者定义一个新函数 function newSum(x){return sum(1,x)}
    如果要改变固定值呢,就需要定义多个新函数: function newSum1(x){return sum(1,x)} , function newSum2(x){return sum(2,x)}
    或者建一个工厂方法 function makeSum(n){return function(x){return sum(n+x);}}  就可以 var sum10=makeSum(10); sum10(1);
    可是这个工厂方法只能产出sum的逻辑,需要一个工厂能够输入什么逻辑,产出这个逻辑,因此可以改进为:
    function bindFirstArg(fn,n){//工厂不仅要接收改变的值,还要接收逻辑
         return function(x){
              return fn(n,x);
         }
    }

      

    可以这么用 
    function add(a,b){return a+b;}
    function del(a,b){return a-b;}
    var add10=bindFirstArg(add,10);add10(1);
    var del10=bindFirstArg(del,10);del10(1);
     
    但是,当可变的参数不止一个是多个,并且个数也不固定的时候,怎么办?
    这就是所谓的Partial Application了。
    Partial application can be described as taking a function that accepts some number of arguments, binding values to one or more of those arguments, and returning a new function that only accepts the remaining, un-bound arguments.
    

    简单来说就是如果我们有函数是多个参数的,我们希望能固定其中某几个参数的值。(比如上文中的newSum1就是sum函数的一个偏函数应用)

    function partial(fn/*,args…..*/){
         var args=Array.prototype.slice.call(arguments,1);//arguments是类数组对象,有length,但是没方法,所以用array的方法切割之,把第一个元素即this去掉
         return function(){
              var nargs=Array.prototype.slice.call(arguments,0);//也是将arguments转为数组,此arguments非上一个arguments
              fn.apply(this,args.contact(nargs));
         }
    }

    代码并不复杂,用法如下:

    function sum(){
         var args=Array.prototype.slice.call(arguments,0),n=0;
         for(var i=0;i<args.length;i++){n+=args[i]}
         return i;
    }
    sum(1,2,3);//普通调用,输出6
    var p=partial(sum,10); p(1,2,3);//输出16
    这么调用之所以可以成功是因为sum函数内部是用arguments来判断传入参数的,而没有写死,如果sum写死参数个数(function sum(a,b){return a+b;}),就会出问题,多于传入的参数会被忽略。
    目前的partial是将参数一次顺序合并(contact),如果是想固定替换一些参数,可以改进为:
    var partialAny=(function(){
         var slice=Array.prototype.slice;//用立即执行函数加闭包先缓存这个方法
         partialAny._={};//这个是用来标示替换的标志
         return function(fn/*,args…..*/){//这个return的fn相当于就是partial
              var orginArgs=slice.call(arguments,1);//partial接收的参数
              return function(){//这个return的fn是最终调用的函数,里边执行了原函数的逻辑
                   var newArgs=slice.call(arguments,0);//
                   var args=[];
                   for(var i=0;i<orginArgs.length;i++)
                   {
                        args[i]=orginArgs[i]===partialAny._?newArgs.shift():orginArgs[i];
                   }
                  return fn.apply(this,args.contact(newArgs));
         } 
    })();

    这个稍微复杂一点,不过看过注释也比较容易明白几次return之间的关系,这里的标示替换的标志“._”略难理解,看完示例就很容易明白了:

    function hex(r, g, b) { return '#' + r + g + b; } 
    hex('11', '22', '33'); // "#112233" 
    var __ = partialAny._; 
    var redMax = partialAny(hex, 'ff', __, __); 
    redMax('11', '22'); // "#ff1122"

    接下来是Currying,它和Partial Application十分相似,不过它是用来解决另一个问题的:如何用多个单参数函数实现一个多参数函数。

    Currying can be described as transforming a function of N arguments in such a way that it can be called as a chain of N functions each with a single argument.
    

     就是说它在接收参数后会判断参数是否满足了原函数所需要的个数,如果不满足,会返回一个函数,其不够的参数仍然有待输入,如果够了,就直接调用原函数。

    function curry(fn,n){
         if(typeof n !==’number’){n=fn.length;}//如果没有认为指定待传的参数个数,就取原函数需要的参数个数
         function getCurriedFn(prev){
              return function(arg){
                   var args=prev.contact(arg);
                   if(args.length<n){
                        return getCurriedFn(args);
                   }
                   else{
                        return fn.apply(this,args);
                   }
              }
         }
         return getCurriedFn([]);
    }

    代码本身也不难理解,其用法如下:

    var i = 0;
    function a(arg1, arg2, arg3) { return ++i + ': ' + arg1 + ', ' + arg2 + ', ' + arg3; } 
    // Normal function invocation. 
    a('x', 'y', 'z'); // "1: x, y, z" 
    a('x', 'y'); // "2: x, y, undefined" 
    a('x'); // "3: x, undefined, undefined" 
    a(); // "4: undefined, undefined, undefined" 
    // Curried function invocation. 
    var b = curry(a); 
    b(); // `a` not invoked, curried function returned 
    b('x'); // `a` not invoked, curried function returned 
    b('x')('y'); // `a` not invoked, curried function returned 
    b('x')('y')('z'); // "5: x, y, z" 
    b('x')('y')(); // "6: x, y, undefined" 
    b('x')()(); // "7: x, undefined, undefined" 
    b()('y')(); // "8: undefined, y, undefined" 
    b()()('z'); // "9: undefined, undefined, z" 
    b()()(); // "10: undefined, undefined, undefined”

    偏函数应用和函数加里化在JS中应用是很少的,因为JS本身就支持多参数函数了,在一些其他语言如Haskell中,多参数函数都是通过加里化实现的,这里能够学习和理解这种思想和技巧,对于学习程序开发有一定的作用。

      这里只是我的一点学习笔记,感谢分享相关知识的前辈和大牛们,他们的文章地址如下:

    http://benalman.com/news/2012/09/partial-application-in-javascript/

    http://alecb.me/blog/currying-partial-application/

  • 相关阅读:
    opengl学习
    同步、异步、多线程与事件型综述
    Javascript异步编程的4种方法
    ASP.NET(C#) GridView (编辑、删除、更新、取消)
    浅析五大ASP.NET数据控件
    用 Eclipse 开发 Android 应用程序
    [C# 网络编程系列]专题十:实现简单的邮件收发器
    [C# 网络编程系列]专题九:实现类似QQ的即时通信程序
    [C# 网络编程系列]专题七:UDP编程补充——UDP广播程序的实现
    [C# 网络编程系列]专题六:UDP编程
  • 原文地址:https://www.cnblogs.com/dson/p/4402560.html
Copyright © 2020-2023  润新知