zoukankan      html  css  js  c++  java
  • JS 函数的柯里化与反柯里化

    =====================================
    函数的柯里化与反柯里化

    [这是一篇比较久之前的总结了,若有错漏,请指正!]

    柯里化 currying

    维基百科的名词解释:柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家哈斯凯尔·加里命名的,尽管它是 Moses Schönfinkel 和 Gottlob Frege 发明的。

    定义:

    函数柯里化,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数。
    

    特点:

    提高了代码的合理性,更重的它突出一种思想---降低适用范围,提高适用性。
    对于一个已有函数,对其约定好其中的某些参数输入,然后生成一个更有好的、更符合业务逻辑的函数。
    

    柯里化(currying),我们可以这么理解:

    柯里化就是一个函数在参数没给全时返回另一个函数,返回的函数的参数正好是余下的参数。
    比如:你制定了x和y, 如2的3次方,就返回8, 如果你只制定x为2,y没指定, 那么就返回一个函数:2的y次方, 这个函数只有一个参数:y。这样就非常容易理解吧。
    
    1. 提高适用性
    2. 延迟执行
    3. 固定易变因素
      http://www.cnblogs.com/pigtail/p/3447660.html - 前端开发者进阶之函数反柯里化unCurrying

    function currying(fn) {
    var __args = Array.prototype.slice.call(arguments, 1);
    return function () {
    var __inargs = slice.call(arguments);
    return fn.apply(null, __args.concat(__inargs));
    };
    }

    提高适用性

        function square(i) {
            return i * i;
        }
    
        function dubble(i) {
            return i *= 2;
        }
    
        function map(handeler, list) {
            return list.map(handeler);
        }
    
        // 数组的每一项平方
        map(square, [1, 2, 3, 4, 5]);
        map(square, [6, 7, 8, 9, 10]);
        map(square, [10, 20, 30, 40, 50]);
    
    
        // 数组的每一项加倍
        map(dubble, [1, 2, 3, 4, 5]);
        map(dubble, [6, 7, 8, 9, 10]);
        map(dubble, [10, 20, 30, 40, 50]);
    
    例子中,创建了一个map通用函数,用于适应不同的应用场景。显然,通用性不用怀疑。
    同时,例子中重复传入了相同的处理函数:square和dubble。
    应用中这种可能会更多。当然,【通用性的增强必然带来适用性的减弱】
    
        使用currying
        function square(i) {
            return i * i;
        }
    
        function dubble(i) {
            return i *= 2;
        }
    
        function map(handeler, list) {
            return list.map(handeler);
        }
    
        var mapSQ = currying(map, square);
        mapSQ([1, 2, 3, 4, 5]);
        mapSQ([6, 7, 8, 9, 10]);
        mapSQ([10, 20, 30, 40, 50]);
        // ......
    
        var mapDB = currying(map, dubble);
        mapDB([1, 2, 3, 4, 5]);
        mapDB([6, 7, 8, 9, 10]);
        mapDB([10, 20, 30, 40, 50]);
        // ......
        【缩小了函数的适用范围,但同时提高了函数的适用性】
    

    延迟执行

        var curry = function(fn){
            var _args = [];
    
            return function(){
                if(arguments.length === 0){
                    return fn.apply(this,_args);
                }
    
                [].push.apply(_args,arguments);
    
                // console.log(arguments.callee);
                return arguments.callee;
            }
        }
    
        var sum=0;
        var add =function(){
            for (var i = 0,c; c=arguments[i++];){
                sum+=c;
            }
            return sum;
        };
    
        var add = curry(add);
    
        var res1 = add(1)(2)(3)();
        console.log(res1)
        // 6
    
        // add(1);
        // add(2);
        // add(3);
        // var res2=add();
        // console.log(res2)
    
    currying函数, 便可以延迟到最后一刻才一起计算, 好处不言而喻, 在很多场合可以避免无谓的计算, 节省性能, 也是实现惰性求值的一种方案.
    

    固定易变因素

    提前把易变因素,传参固定下来,生成一个更明确的应用函数。最典型的代表应用,是bind函数用以固定this这个易变对象。
    Function.prototype.bind = function(context) {
        var _this = this,
            _args = Array.prototype.slice.call(arguments, 1);        
        return function() {
             return _this.apply(context, _args.concat( Array.prototype.slice.call(arguments)));
        }
    }
    
    bind函数本身就是柯里化的一种体现,函数可以通过bind绑定新的上下文环境来改变其所处的上下文,并生成一个新的函数。这里第一次传入的参数_args就是函数本身的信息,被保存在了函数的闭包之中,就是柯里化的属性;之后的第二个参数arguments,就是在函数调用的过程中再传递的剩余参数。
    

    反柯里化 uncurrying

    反科里化的话题来自javascript之父Brendan Eich去年的一段twitter.
    

    参考

    http://www.jb51.net/article/32435.htm - javascript中有趣的反柯里化深入分析
    http://sombie.diandian.com/post/2013-06-28/40050585369 -【WEB前端】由JavaScript反柯里化所想到的

    定义

    让你自定义的对象拥有原生JS对象的方法,并利用鸭子类型的特征扩展其使用范围。

    作用

    扩展函数适用范围的方法
    把函数也当作普通数据来使用, 当函数名本身是个变量的时候, 这种调用方法特别方便.
    扩大函数的适用性,使本来作为特定对象所拥有功能的函数可以对全体对象使用
    
    Function.prototype.uncurry = function() {
        var _this = this
        return function() {
            return Function.prototype.call.apply(_this, arguments)
        }   
    }
    
    短小精悍,科学上讲,浓缩的都是精品,但越精品的往往越难以理解。分解一下:
    1 为Function原型添加unCurrying方法,这样所有的function都可以被借用;
    2 返回一个借用其它方法的函数,这是目的;
    3 借用call方法实现,但call方法参数传入呢?借用apply,至此完毕。
    

    最终是把this.method转化成 method(this,arg1,arg2....)以实现方法借用和this的泛化。

    用法

    foo = somefun.uncurry();
    foo(obj, args...) <==> obj.somefun(args)。

    例:让Object也拥有push的方法
    var push = Array.prototype.push.uncurry()
    push({},'aa');

    反柯里化的最简方式实现

    //将原生的bind函数转换为全局的bind
    var bind = Function.prototype.call.bind(Function.prototype.bind);
    module={
        debug:function(){
            console.log.apply(console,arguments);
        }
    };
    var debug = bind(module.debug,module);
    
    debug('adsfadsf','444',[1,2,3]);
    //输出:adsfadsf 444 [1, 2, 3] 
    
    //将原生的push函数转换为全局的push
    var push = Function.prototype.call.bind([].push);
    
    var person ={
        name:'aa',
        age:33
    };
    push(person,'44');
    
    debug(person);
    //输出:Object {0: "44", name: "aa", age: 33, length: 1} 
    
    为什么push函数可以应用到非数组对象上?
    v8引擎里面Array.prototype.push的代码
    function ArrayPush() { 
        var n = TO_UINT32( this.length ); 
        var m = %_ArgumentsLength(); 
        for (var i = 0; i < m; i++) { 
            this[i+n] = %_Arguments(i); //属性拷贝 
            this.length = n + m; //修正length 
            return this.length; 
        } 
    }
    ArrayPush方法没有对this的类型做任何显示的限制,所以理论上任何对象都可以被传入ArrayPush这个访问者。
    push可以应该到类似数组类型的对象上,即鸭子类型
    

    动态语言中重要的鸭子类型思想

    故事:
    很久以前有个皇帝喜欢听鸭子呱呱叫,于是他召集大臣组建一个一千只鸭子的合唱团。大臣把全国的鸭子都抓来了,最后始终还差一只。有天终于来了一只自告奋勇的鸡,这只鸡说它也会呱呱叫,好吧在这个故事的设定里,它确实会呱呱叫。 后来故事的发展很明显,这只鸡混到了鸭子的合唱团中。— 皇帝只是想听呱呱叫,他才不在乎你是鸭子还是鸡呢。
    这个就是鸭子类型的概念,在javascript里面,很多函数都不做对象的类型检测,而是只关心这些对象能做什么。
    Array构造器和String构造器的prototype上的方法就被特意设计成了鸭子类型。这些方法不对this的数据类型做任何校验。这也就是为什么arguments能冒充array调用push方法.

  • 相关阅读:
    P4932 浏览器 题解
    P1627 [CQOI2009]中位数 题解
    P4626 一道水题 II 题解
    P1439 【模板】最长公共子序列 题解
    P2324 [SCOI2005]骑士精神 题解
    P1784 数独 题解
    浅谈 Dancing Links X 算法
    P5905 【模板】Johnson 全源最短路 题解
    线性预处理阶乘,逆元和组合数
    需要支持多种操作的线段树该如何确定运算顺序?
  • 原文地址:https://www.cnblogs.com/lt-style/p/4368564.html
Copyright © 2011-2022 走看看