
柯里化的概念
在函數(shù)式編程(Functional Programming)相關(guān)的文章中,經(jīng)常能看到柯里化 (Currying)這個名詞。它是數(shù)學(xué)家柯里(Haskell Curry)提出的。
柯里化,用一句話解釋就是,把一個多參數(shù)的函數(shù)轉(zhuǎn)化為單參數(shù)函數(shù)的方法。
這是一個兩個參數(shù)的普通函數(shù):
function plus(x, y){
return x + y
}
plus(1, 2) // 輸出 3
經(jīng)過柯里化后這個函數(shù)變成這樣:
function plus(y){
return function (x){
return x + y
}
}
plus(1)(2) // 輸出 3
復(fù)合函數(shù)與柯里化
剛開始接觸的時候,第一感覺和泰勒級數(shù) (Taylor Series) 展開有些相似,然后我就去高數(shù)的書本里邊翻,然而并沒有 :) 實(shí)際上,編程相關(guān)的文章,只要提到柯里化,就是指的把一個多參數(shù)的函數(shù)轉(zhuǎn)化為單參數(shù)函數(shù)的方法,只不過,文章中順便提及了柯里化的由來,再加上 Haskell 高大上的編程語言,總覺著這得是個多復(fù)雜的公式。
復(fù)合函數(shù),中數(shù)學(xué)課本就有(忘記是高中還是初中了,反正大學(xué)學(xué)的都忘記了 :p),形式上是這樣:
$$F(x) = f(g(x))$$
可以這么理解這個函數(shù),從西安去北京,可以乘坐直達(dá)車,也可以從鄭州中轉(zhuǎn)。$$F(x)$$就是整個旅程,$$g(x)$$是從西安到鄭州,$$f(x)$$是從鄭州到北京。如果把公式用編程語言表示,就是這樣:
function travel(point2){
let dest = '北京'
return function(point1){
return point1 + '->' + point2
+ '->' + dest
}
}
travel('鄭州')('西安') //輸出 西安->鄭州->北京
柯里化的作用
- 惰性求值 (Lazy Evaluation)
從上文的代碼來看,柯里化收的函數(shù)是分步執(zhí)行的,第一次調(diào)用返回的是一個函數(shù),第二次調(diào)用的時候才會進(jìn)行計算。起到延時計算的作用,通過延時計算求值,稱之為惰性求值。
- 動態(tài)生成函數(shù)
假如實(shí)際編程中需要求不通數(shù)的若干次冪(整數(shù)),可能需要求2次冪,也能需要4次冪或者其他次冪,如果不用柯里化,那么需要求幾次冪,就得寫幾個對應(yīng)方法。通過柯里化,可以寫在一個方法中:
function power(n){
return function (number){
let result = 1;
for(let i = 0; i < n; ++i){
result *= number;
}
return result;
}
}
需要求平方的時候,可以直接生成一個求平方的方法。
let p2 = power(2);
p2(4) // 輸出16
p2(5) // 輸出25
同樣,需要求立方也可以直接生成一個求立方的方法,不用每個冪次都寫一個方法。
let p3 = power(3); //求立方
p3(2) // 輸出8
p3(4) // 輸出64
柯里化與閉包
閉包 (Closure) 在前端圈討論的非常的多,不過多數(shù)為剛?cè)腴T的同學(xué)。閉包的概念,看專業(yè)的解釋非常的復(fù)雜:
閉包包含自由(未綁定到特定對象)變量;這些變量不是在這個代碼塊內(nèi)或者任何全局上下文中定義的,而是在定義代碼塊的環(huán)境中定義(局部變量)。“閉包” 一詞來源于以下兩者的結(jié)合:要執(zhí)行的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和為自由變量提供綁定的計算環(huán)境(作用域)。
這一大段話濃縮成一句話是,在一個函數(shù)內(nèi)部定義一個局部變量,并通過一個函數(shù)將其返回。很明細(xì)上文提到的動態(tài)生成函數(shù)中用的例子就是一個閉包函數(shù)。
從形式來看,柯里化和閉包非常的相似,這二者有什么關(guān)系呢?
沒什么關(guān)系,只不過看起來有些像。事實(shí)上,網(wǎng)絡(luò)上大多的文章討論柯里化與閉包都是分開來談的,幾乎沒有明確的說明,他倆究竟有什么關(guān)系。首先,這是兩個不通的概念,通過定義來看,作用也不同。只不過都用到了匿名函數(shù)。
柯里化與Lambda表達(dá)式
柯里化也經(jīng)常與$$λ$$ (Lambda)表達(dá)式一起使用。提到Lambda表達(dá)式就必須得提到匿名函數(shù)。
匿名函數(shù),在底層語言中不是現(xiàn)成的,比如C語言中就沒有匿名函數(shù),因?yàn)镃語言在函數(shù)調(diào)用之前都得聲明一下,或者把函數(shù)定義在調(diào)用之前,所以也就談不上匿名函數(shù)了:p,C++ 11中才引入了Lambda表達(dá)式,支持匿名函數(shù)。所以一些資料有時也把匿名函數(shù)直接等同于Lambda表達(dá)式。
有種觀點(diǎn)認(rèn)為:
從純粹的語義上,柯里化就是Lambda表達(dá)式的一個糖
這里其實(shí)就是把Lambda當(dāng)作是匿名函數(shù)了。嚴(yán)格的來說,不是所有的匿名函數(shù)都是Lambda表達(dá)式。C# 2.0中引入了委托與匿名函數(shù),3.0之后才引入Lambda表達(dá)式。
list.Where( delegate( object item ) {
return item != null;
});
這個是委托的寫法
list.Where( item => item != null );
這個才是Lambda的寫法(這里補(bǔ)充一點(diǎn),經(jīng)過自己的實(shí)踐,其實(shí)在.NET 2.0的平臺也可以使用Lambda表達(dá)式,只不過編譯的時候調(diào)用的是高版本的編譯器,所以在.NET 2.0的工程中使用高版本的C#特性,編譯和運(yùn)行都不會有問題)
Lambda表達(dá)式有固定的規(guī)范,寫成 lambda x . body (prefix args separator expression)表示一個參數(shù)參數(shù)為 x 的函數(shù),它的返回值為 body 的計算結(jié)果。在不同的語言中Lambda表達(dá)式的形式也是不同的,比如Python:
lambda x : x + 1
prefix 是可選的, 再比如Java
item -> item != null
這么說來,柯里化與Lambda表達(dá)式也沒有關(guān)系。
柯里化在Lambda表達(dá)式中應(yīng)用
從上文來看,純正的Lambda表達(dá)式的規(guī)范,是只有個一個入?yún)⒌?,如果多個參數(shù)怎么辦呢?
柯里化的作用就能體現(xiàn)出來了,比如要實(shí)現(xiàn)一個兩個數(shù)求和的表達(dá)式,用純正的Lambda表達(dá)式,可以寫成這樣:
let foo = (x) => {
return y => x + y
}
foo(1)(2) //輸出 3
這么顯然有些麻煩,實(shí)踐中是這么寫的
let foo = (x, y) => x + y
foo(1, 2) //輸出 3
是不是實(shí)踐中的寫法不符合Lambda表達(dá)式的規(guī)范呢?
可以理解為第二種寫法為第一種寫法的簡寫,是語法糖,第一種單個參數(shù)的寫法是脫糖后的形式。