什么是柯里化

2017年11月 國子監(jiān)

柯里化的概念

在函數(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 . bodyprefix 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ù)的寫法是脫糖后的形式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容