柯里化详解
在計(jì)算機(jī)科學(xué)中,柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。這個(gè)技術(shù)由 Christopher Strachey 以邏輯學(xué)家 Haskell Curry 命名的,盡管它是 Moses Schnfinkel 和 Gottlob Frege 發(fā)明的。
柯里化(Currying)指的是將原來接受兩個(gè)參數(shù)的函數(shù)變成新的接受一個(gè)參數(shù)的函數(shù)的過程。新的函數(shù)返回一個(gè)以原有第二個(gè)參數(shù)為參數(shù)的函數(shù)。
柯里化是很多高級編程語言擁有的特性,比如:JS、scala。
柯里化的好處:
1、參數(shù)復(fù)用。
2、提前返回。
3、 延遲計(jì)算/運(yùn)行。
函數(shù)柯里化(currying)又稱部分求值。一個(gè) currying 的函數(shù)首先會接受一些參數(shù),接受了這些參數(shù)之后,該函數(shù)并不會立即求值,而是繼續(xù)返回另外一個(gè)函數(shù),剛才傳入的參數(shù)在函數(shù)形成的閉包中被保存起來。待到函數(shù)被真正需要求值的時(shí)候,之前傳入的所有參數(shù)都會被一次性用于求值。
例如:
有一個(gè)函數(shù),功能是計(jì)算每月的開銷,我們實(shí)際關(guān)心的是整個(gè)月的開銷總額。
var monthlyCost = 0;
var cost = function( money ){
monthlyCost += money;
};
cost( 100 ); // 第 1 天開銷
cost( 200 ); // 第 2 天開銷
cost( 300 ); // 第 3 天開銷
//cost( 700 ); // 第 30 天開銷
alert ( monthlyCost ); // 輸出:600
通過這段代碼可以看到,每天結(jié)束后我們都會記錄并計(jì)算到今天為止花掉的錢。但我們其實(shí)并不太關(guān)心每天花掉了多少錢,而只想知道到月底的時(shí)候會花掉多少錢。也就是說,實(shí)際上只需要在月底計(jì)算一次。
如果在每個(gè)月的前 29 天,我們都只是保存好當(dāng)天的開銷,直到第 30 天才進(jìn)行求值計(jì)算,這樣就達(dá)到了我們的要求。雖然下面的 cost 函數(shù)還不是一個(gè) currying 函數(shù)的完整實(shí)現(xiàn),但有助于我們了解其思想:
var cost = (function(){
var args = [];
return function(){
if ( arguments.length === 0 ){
var money = 0;
for ( var i = 0, l = args.length; i < l; i++ ){
money += args[ i ];
}
return money;
}else{
[].push.apply( args, arguments );
}
}
})();
cost( 100 ); // 未真正求值
cost( 200 ); // 未真正求值
cost( 300 ); // 未真正求值
console.log( cost() ); // 求值并輸出:600
柯里化的通用方式:
function curry(fn){
var args=Array.prototype.slice.call(arguments,1);
return function(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null,finalArgs);
);
}
function add(n1,n2){
return n1+n2;
}
var cur=curry(add,5)
alert(cur(3))
上面的代碼就完成了對add函數(shù)的柯理化,其原理十分簡單。無非就是把上一層傳入的參數(shù)再連接起來,傳回原來的多參數(shù)函數(shù)。第一步args代表的就是要保留的那個(gè)參數(shù)。
使用函數(shù)柯里化解決問題
var currying = function( fn ){
var args = [];
return function(){
if ( arguments.length === 0 ){
return fn.apply( this, args );
}else{
[].push.apply( args, arguments );
return arguments.callee;
}
}
};
var cost = (function(){
var money = 0;
return function(){
for ( var i = 0, l = arguments.length; i < l; i++ ){
money += arguments[ i ];
}
return money;
}
})();
var cost = currying( cost ); // 轉(zhuǎn)化成 currying 函數(shù)
cost( 100 ); // 未真正求值
cost( 200 ); // 未真正求值
cost( 300 ); // 未真正求值
alert ( cost() ); // 求值并輸出:600
至此,我們完成了一個(gè) currying 函數(shù)的編寫。當(dāng)調(diào)用 cost()時(shí),如果明確地帶上了一些參數(shù),表示此時(shí)并不進(jìn)行真正的求值計(jì)算,而是把這些參數(shù)保存起來,此時(shí)讓 cost 函數(shù)返回另外一個(gè)函數(shù)。只有當(dāng)我們以不帶參數(shù)的形式執(zhí)行 cost()時(shí),才利用前面保存的所有參數(shù),真正開始進(jìn)行求值計(jì)算。
Scala 中的柯里化
# 定義一個(gè)函數(shù)
函數(shù)1 :def add(x:Int,y:Int)=x+y
# 把函數(shù)變形:
函數(shù)2:def add(x:Int)(y:Int) = x + y
# 再變形:
函數(shù)3:def add(x:Int)=(y:Int)=>x+y
這3個(gè)函數(shù)在scala里都是支持的,函數(shù)1和函數(shù)2是普通函數(shù),函數(shù)3是柯里化函數(shù),我們看下執(zhí)行上的區(qū)別:
# 函數(shù)1 :def add(x:Int,y:Int)=x+y
scala> def add(x:Int,y:Int)=x+y
add: (x: Int, y: Int)Int
scala> add(2,3)
res0: Int = 5
# 函數(shù)2:def add(x:Int)(y:Int) = x + y
scala> def add(x:Int)(y:Int)=x+y
add: (x: Int)(y: Int)Int
scala> add(2)(3)
res0: Int = 5
# 函數(shù)3:def add(x:Int)=(y:Int)=>x+y
scala> def add(x:Int)=(y:Int)=>x+y
add: (x: Int)Int => Int
scala> add(2)
res0: Int => Int = $$Lambda$1058/1404150776@7d97e06c
scala> add(3)
res1: Int => Int = $$Lambda$1058/1404150776@523a7801
scala> add(2)(3)
res2: Int = 5
通過上面的計(jì)算過程可以看出,函數(shù)3,也是需要傳2個(gè)參數(shù)的,如果傳了2個(gè)參數(shù),馬上會計(jì)算出結(jié)果;如果只傳了一個(gè)參數(shù),那么會生成一個(gè)臨時(shí)的結(jié)果res0,這里面并沒有把結(jié)果計(jì)算出來,而是把運(yùn)算緩存起來了,當(dāng)?shù)诙€(gè)參數(shù)也傳進(jìn)來了,就會開始計(jì)算最終結(jié)果。
再舉上面的計(jì)算每月總開銷的例子,柯里化函數(shù)并不關(guān)心中間結(jié)果,也不會去計(jì)算中間結(jié)果,只會做一個(gè)緩存計(jì)算操作的操作,在最終需要執(zhí)行的時(shí)候才去執(zhí)行
如果你有更好的理解柯里化的方式,歡迎在評論區(qū)交流。
總結(jié)
- 上一篇: MS17010补丁列表-转载
- 下一篇: 用boost库实现traceroute小