目錄
一、初識Currying柯里化
柯里化(Currying)技術 Christopher Strachey 以邏輯學家 Haskell Curry 命名的(儘管它是 Moses Schnfinkel 和 Gottlob Frege 發明的)。它是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數且返回結果的新函數的技術。
簡單理解就是改變函數的表達形式但其功能特性不變,這對於第一次接觸柯里化的人來講,這樣的一個技術貌似有點“雞肋”,但如果你有豐富的JS編程經驗的話,相信就一定會感受到柯里化其實是具有很高的實用性的。無論是在提高適用性還是在延遲執行或者固定易變因素等方面,柯里化技術都發揮着重要的作用。
二、從案例角度學習scala柯里化
還是老觀點——無論是修煉新技術還是舊理論,結合小案例理解學習,纔是最有效的方法。下面將通過幾個例子來學習scala柯里化。
2.1、從2個整數相乘運算的函數來揭開柯里化的神祕面紗
在scala中定義2個整數相乘運算的函數是非常簡單的,具體如下:
def multiplie2par(x:Int,y:Int)=x*y
使用柯里化技術可以將上述2個整數的乘法函數改修爲接受一個參數的函數,只是該函數返回是一個以原有第二個參數爲參數的函數。
def multiplie1par(x:Int)=(y:Int)=>x*y
【ps說明:multiplie1par(x:Int)爲接收一個參數的新等價函數,(y:Int)=>x*y則是新等價函數的返回體,它本身就是一個函數(嚴格來說應該是一個匿名函數),參數爲除等價新函數的參數外原函數剩餘的參數。】
上述使用柯里化技術得到等價新函數,在scala語言中看起來還不是很簡潔,在scala中可以進一步簡化爲:
def multiplie1par1(x:Int)(y:Int)=x*y
編譯運行的結果如下:
同樣對於三個整數乘法的函數在scala中由於有柯里化的存在,自然有多種功能等價的函數定義形式,如以下四種函數都是實現了三個整數乘法功能,只不過調用不同形式參數過程略有不同,直接參入三個參數的一步到位就可以得到運算結果,而傳入1或者2個參數的需要分步驟再傳入第2/3或者第3個參數才能求出三個整數相乘的結果,很好地體現了延遲執行或者固定易變因素等方面能力。
def multiplie3par(x:Int,y:Int,z:Int)=x*y*z
def multiplie3par1(x:Int)=(y:Int,z:Int)=>x*y*z
def multiplie3par2(x:Int)(y:Int)(z:Int)=x*y*z
def multiplie3par3(x:Int)(y:Int)=(z:Int)=>x*y*z
編譯執行的結果都是一樣
2.2、scala中柯里化的另一種典型案例——correspondsf方法的柯里化應用
在scala中corresponds方法能使得兩個序列按照一定的條件進行比較,該方法其實也是經過柯里化參數的。在scala的API中該方法定義如下:在方法簽名描述中有兩個參數,that序列和p函數,其中p函數有兩個參數,第二個參數類型是與that序列一致的。應用柯里化,我們可以省去第二個參數中B的類型,因爲從that序列中推斷出B的類型。
def corresponds[B](that: GenSeq[B])(p: (T, B) ⇒ Boolean): Boolean
因此假如有字符串類型序列a和字符串序列b,我們可以使用a. corresponds(b)(_. equalsIgnoreCase(_))來判斷兩個字符串序列在不區分大小的情況是否一致,這就爲編程帶來了“魔法般”的靈活性。
三、總結柯里化的作用
3.1、柯里化技術在提高適用性還是在延遲執行或者固定易變因素等方面有着重要重要的作用,加上scala語言本身就是推崇簡潔編碼,使得同樣功能的函數在定義與轉換的時候會更加靈活多樣。另外在Spark的源碼中有大量運用scala柯里化技術的情況,需要掌握好該技術才能看得懂相關的源代碼。
3.2、在scala柯里化中,閉包也發揮着重要的作用。所謂的閉包就是變量出了函數的定義域外在其他代碼塊還能其作用,這樣的情況稱之爲閉包。就上述討論的案例而言,如果沒有閉包作用,那麼轉換後函數其實返回的匿名函數是無法在與第一個參數x相關結合的,自然也就無法保證其所實現的功能是跟原來一致的。