JS 函數的柯里化與反柯里化

=====================================
函數的柯里化與反柯里化
=====================================
[這是一篇比較久以前的總結了,如有錯漏,請指正!]javascript

柯里化 currying

維基百科的名詞解釋:柯里化(英語:Currying),又譯爲卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。這個技術由 Christopher Strachey 以邏輯學家哈斯凱爾·加里命名的,儘管它是 Moses Schönfinkel 和 Gottlob Frege 發明的。html

定義:

函數柯里化,是固定部分參數,返回一個接受剩餘參數的函數,也稱爲部分計算函數,目的是爲了縮小適用範圍,建立一個針對性更強的函數。

特色:

提升了代碼的合理性,更重的它突出一種思想---下降適用範圍,提升適用性。
對於一個已有函數,對其約定好其中的某些參數輸入,而後生成一個更有好的、更符合業務邏輯的函數。

柯里化(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反柯里化所想到的java

定義

讓你自定義的對象擁有原生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)。app

例:讓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方法.post

相關文章
相關標籤/搜索