柯里化详解
在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數且返回結果的新函數的技術。這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的,盡管它是 Moses Schnfinkel 和 Gottlob Frege 發明的。
柯里化(Currying)指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個以原有第二個參數為參數的函數。
柯里化是很多高級編程語言擁有的特性,比如:JS、scala。
柯里化的好處:
1、參數復用。
2、提前返回。
3、 延遲計算/運行。
函數柯里化(currying)又稱部分求值。一個 currying 的函數首先會接受一些參數,接受了這些參數之后,該函數并不會立即求值,而是繼續返回另外一個函數,剛才傳入的參數在函數形成的閉包中被保存起來。待到函數被真正需要求值的時候,之前傳入的所有參數都會被一次性用于求值。
例如:
有一個函數,功能是計算每月的開銷,我們實際關心的是整個月的開銷總額。
var monthlyCost = 0;
var cost = function( money ){
monthlyCost += money;
};
cost( 100 ); // 第 1 天開銷
cost( 200 ); // 第 2 天開銷
cost( 300 ); // 第 3 天開銷
//cost( 700 ); // 第 30 天開銷
alert ( monthlyCost ); // 輸出:600
通過這段代碼可以看到,每天結束后我們都會記錄并計算到今天為止花掉的錢。但我們其實并不太關心每天花掉了多少錢,而只想知道到月底的時候會花掉多少錢。也就是說,實際上只需要在月底計算一次。
如果在每個月的前 29 天,我們都只是保存好當天的開銷,直到第 30 天才進行求值計算,這樣就達到了我們的要求。雖然下面的 cost 函數還不是一個 currying 函數的完整實現,但有助于我們了解其思想:
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函數的柯理化,其原理十分簡單。無非就是把上一層傳入的參數再連接起來,傳回原來的多參數函數。第一步args代表的就是要保留的那個參數。
使用函數柯里化解決問題
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 ); // 轉化成 currying 函數
cost( 100 ); // 未真正求值
cost( 200 ); // 未真正求值
cost( 300 ); // 未真正求值
alert ( cost() ); // 求值并輸出:600
至此,我們完成了一個 currying 函數的編寫。當調用 cost()時,如果明確地帶上了一些參數,表示此時并不進行真正的求值計算,而是把這些參數保存起來,此時讓 cost 函數返回另外一個函數。只有當我們以不帶參數的形式執行 cost()時,才利用前面保存的所有參數,真正開始進行求值計算。
Scala 中的柯里化
# 定義一個函數
函數1 :def add(x:Int,y:Int)=x+y
# 把函數變形:
函數2:def add(x:Int)(y:Int) = x + y
# 再變形:
函數3:def add(x:Int)=(y:Int)=>x+y
這3個函數在scala里都是支持的,函數1和函數2是普通函數,函數3是柯里化函數,我們看下執行上的區別:
# 函數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
# 函數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
# 函數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
通過上面的計算過程可以看出,函數3,也是需要傳2個參數的,如果傳了2個參數,馬上會計算出結果;如果只傳了一個參數,那么會生成一個臨時的結果res0,這里面并沒有把結果計算出來,而是把運算緩存起來了,當第二個參數也傳進來了,就會開始計算最終結果。
再舉上面的計算每月總開銷的例子,柯里化函數并不關心中間結果,也不會去計算中間結果,只會做一個緩存計算操作的操作,在最終需要執行的時候才去執行
如果你有更好的理解柯里化的方式,歡迎在評論區交流。
總結
- 上一篇: MS17010补丁列表-转载
- 下一篇: 树莓派 配置 OMV 搭建 NAS(六)