1. 程式人生 > >js高階函式應用—函式柯里化和反柯里化

js高階函式應用—函式柯里化和反柯里化

轉載自shunfa888

在Lambda演算(一套數理邏輯的形式系統,具體我也沒深入研究過)中有個小技巧:假如一個函式只能收一個引數,那麼這個函式怎麼實現加法呢,因為高階函式是可以當引數傳遞和返回值的,所以問題就簡化為:寫一個只有一個引數的函式,而這個函式返回一個帶引數的函式,這樣就實現了能寫兩個引數的函數了(具體參見下邊程式碼)——這就是所謂的柯里化(Currying,以邏輯學家Hsakell Curry命名),也可以理解為一種在處理函式過程中的邏輯思維方式。

複製程式碼
function add(a, b) {
return a + b;
}

//函式只能傳一個引數時候實現加法
function curry(a) {
return function(b) {
return a + b;
}
}
var add2 = curry(2); //add2也就是第一個引數為2的add版本
console.log(add2(3))//5
複製程式碼
通過以上簡單介紹我們大概瞭解了,函式柯里化基本是在做這麼一件事情:只傳遞給函式一部分引數來呼叫它,讓它返回一個函式去處理剩下的引數。用公式表示就是我們要做的事情其實是

fn(a,b,c,d)=>fn(a)(b)©(d);

fn(a,b,c,d)=>fn(a,b)©(d);

fn(a,b,c,d)=>fn(a)(b,c,d);

再或者這樣:

fn(a,b,c,d)=>fn(a)(b)©(d)();

fn(a,b,c,d)=>fn(a);fn(b);fn©;fn(d);fn();

但不是這樣:

fn(a,b,c,d)=>fn(a);

fn(a,b,c,d)=>fn(a,b);

這類不屬於柯里化內容,它也有個專業的名字叫偏函式,這個之後我們也會提到。

下面我們繼續把之前的add改為通用版本:

const curry = (fn, …arg) => {
let all = arg;
return (…rest) => {
all.push(…rest);
return fn.apply(null, all);
}
}
let add2 = curry(add, 2)
console.log(add2(8)); //10
add2 = curry(add);
console.log(add2(2,8)); //10

如果你想給函式執行繫結執行環境也很簡單,可以多傳入個引數:

const curry = (fn, constext, …arg) => {
let all = arg;
return (…rest) => {
all.push(…rest);
return fn.apply(constext, all);
}
}

不過到目前我們並沒有實現柯里化,就是類似fn(a,b,c,d)=>fn(a)(b)©(d),這樣的轉化,原因也很明顯,我們curry之後的add2函式只能執行一次,不能夠sdd2(5)(8)這樣執行,因為我們沒有在函式第一次執行完後返回一個函式,而是返回的值,所以無法繼續呼叫。

所以我們繼續實現我們的curry函式,要實現的點也明確了,柯里化後的函式在傳入引數未達到柯里化前的個數時候我們不能返回值,應該返回函式讓它繼續執行(如果你閱讀到這裡可以試著自己實現一下),下面給出一種簡單的實現方式:

const curry = (fn, …arg) => {
let all = arg || [],
length = fn.length;
return (…rest) => {
let _args = all.slice(0); //拷貝新的all,避免改動公有的all屬性,導致多次呼叫_args.length出錯
_args.push(…rest);
if (_args.length < length) {
return curry.call(this, fn, …_args);
} else {
return fn.apply(this, _args);
}
}
}
let add2 = curry(add, 2)
console.log(add2(8));
add2 = curry(add);
console.log(add2(2, 8));
console.log(add2(2)(8));
let test = curry(function(a, b, c) {
console.log(a + b + c);
})
test(1, 2, 3);
test(1, 2)(3);
test(1)(2)(3);

這裡程式碼邏輯其實很簡單,就是判斷引數是否已經達到預期的值(函式柯里化之前的引數個數),如果沒有繼續返回函式,達到了就執行函式然後返回值,唯一需要注意的點我在註釋裡寫出來了all相當於閉包引用的變數是公用的,需要在每個返回的函式裡拷貝一份;

好了到這裡我們基本實現了柯里化函式,我們來看文章開始羅列的公式,細心的同學應該能發現:

fn(a,b,c,d)=>fn(a)(b)©(d)();//mod1

fn(a,b,c,d)=>fn(a);fn(b);fn©;fn(d);fn();//mod2

這兩種我們的curry還未實現,對於這兩個公式其實是一樣的,寫法不同而已,對比之前的實現就是多了一個要素,函式執行返回值的觸發時機和被柯里化函式的引數的不確定性,好了我們來簡單修改一下程式碼:

const curry = (fn, …arg) => {
let all = arg || [],
length = fn.length;
return (…rest) => {
let _args = all;
_args.push(…rest);
if (rest.length === 0) {
       all=[];
return fn.apply(this, _args);
} else {
return curry.call(this, fn, …_args);
}
}
}
let test = curry(function(…rest) {
let args = rest.map(val => val * 10);
console.log(args);
})
test(2);
test(2);
test(3);
test();
test(5);
test();
test(2)(2)(2)(3)(4)(5)(6)();
test(2, 3, 4, 5, 6, 7)();

現在我們這個test函式的引數就可以任意傳,可多可少,至於在什麼時候執行返回值,控制權在我們(這裡是設定的傳入引數為空時候觸發函式執行返回值),當然根據這邏輯我們能改造出來很多我們期望它按我們需求傳參、執行的函式——這裡我們就體會到了高階函式的靈活多變,讓使用者有更多發揮空間。

到這裡我們科裡化基本說完了,下面我們順帶說一下偏函式,如果你上邊柯里化的程式碼都熟悉了,那麼對於偏函式的這種轉化形式應該得心應手了:

fn(a,b,c,d)=>fn(a);

fn(a,b,c,d)=>fn(a,b);

我們還是先來看程式碼吧

function part(fn, …arg) {
let all = arg || [];
return (…rest) => {
let args = all.slice(0);
args.push(…rest);
return fn.apply(this, args)
}
}

function add(a = 0, b = 0, c = 0) {
console.log(a + b + c);
}
let addPart = part(add);
addPart(9); //9
addPart(9, 11);//20

很簡單了,我們現在的addPar就能隨便傳參都能呼叫了,當然我們也能控制函式之呼叫某一個或者多個引數,例如這樣:

如上圖所示,我們想用parseInt幫我們轉化個數組,但是我們有沒發改動parseInt的程式碼,所以控制一下傳參就行了,這樣我們map就傳入的引數只取到第一個,得到了我們的期望值。

http://www.zht3189.cn
http://www.zzl7747.cn
http://www.abd5921.cn
http://www.hch9349.cn
http://www.hwp3498.cn
http://www.wll1115.cn
http://www.rol3427.cn
http://www.akb6775.cn
http://www.giy4971.cn
http://www.tyo9948.cn
http://www.uzh3227.cn
http://www.mfc7569.cn
http://www.otm3953.cn
http://www.ewh1005.cn
http://www.iip1291.cn
http://www.dyg4913.cn
http://www.ase4727.cn
http://www.uqx4260.cn
http://www.sif0574.cn
http://www.sdb0307.cn
http://www.thg4282.cn
http://www.rvc0755.cn
http://www.qld9407.cn
http://www.idd5091.cn
http://www.cqu4082.cn
http://www.skk3561.cn
http://www.lsz6488.cn
http://www.ncc8754.cn
http://www.tsx6039.cn
http://www.ayr4754.cn
http://www.cjd7774.cn
http://www.wzf9854.cn
http://www.nzl1119.cn
http://www.zdn2144.cn
http://www.ief1694.cn
http://www.buu7798.cn
http://www.eig6365.cn
http://www.djf2649.cn
http://www.hvc6084.cn
http://www.opg6486.cn
http://www.hud3144.cn
http://www.iit3286.cn
http://www.wyu4949.cn
http://www.lus6696.cn
http://www.epi0997.cn
http://www.rdk6709.cn
http://www.lwa7903.cn
http://www.kjx4882.cn
http://www.nwf3326.cn
http://www.thy6127.cn
http://www.xho5322.cn
http://www.bfc2814.cn
http://www.lhl7110.cn
http://www.kpx1618.cn
http://www.prl0026.cn
http://www.bxb7451.cn
http://www.ube1531.cn
http://www.qnu9925.cn