引子:在上一篇文章中,咱們使用到了"Curry"。若是你看了這個框架的源代碼的話,可能有點犯暈(有可能只有我一我的這樣,你們都是大神)。這篇文章就是關於這個「柯里化」的內容,參考了庫做者的博客以及喵神的 tips 和Ole Begemann的文章。順便說一句喵神出書,能夠去支持一下。git
首先,咱們來看個簡單的例子:github
func add(a: Int,b: Int) -> Int{ return a + b }
這個函數很容易理解,就是一個整型求和的函數,函數接收兩個整型做爲參數,並返回兩個參數相加的結果。咱們能夠直接使用該函數:swift
let sum: Int = add(2,b: 3) //sum = 5
當咱們但願將一個整數數組裏面的全部數據都增長一個基數的時候,咱們能夠進行以下的操做:數組
let xs = 1...10 let x = xs.map { return add($0,b: 2) } // x = [3,4,5,etc]
在代碼中,咱們先聲明並初始化了一個整型數組,而後用map方法是數組裏面的元素都增長了2,並將結果保存到新的數組中。其中$0表示當前迭代到的元素。在代碼中,咱們使用了閉包(closure),而且傳遞了一個默認參數。可是當在多處使用相似功能的時候,每次都寫閉包仍是不夠精簡。下面作第一步改進:閉包
func addTwo(a: Int) -> Int { return add(a, 2) } let xs = 1...10 let x = xs.map(addTwo) // x = [3,4,5,etc]
可是這個改進有明細的不足,就是默認參數是寫死的。當咱們要將默認參數設爲3的時候咱們不得從新寫一個函數addThree,這顯然不符合代碼複用的要求。咱們進一步作出修改:框架
func add(a: Int) -> (Int -> Int) { return { b in a + b } }
這個函數看起來是否是有點暈啊。咱們來一步步分析一下這個函數,首先,函數接收一個整型參數a,函數的返回值是一個Int -> Int 的函數類型,在這個返回的函數類型也會傳入一個參數,以及返回一個值(參數a是函數add傳入,參數b是閉包裏的,返回值是a + b),這意味這兩件事:函數
若是咱們直接調用該函數的化,咱們須要使用兩個括號來區分這兩個參數,形如:spa
let sum = add(2)(3) //sum = 5
咱們能夠經過傳遞一個參數個該函數,那麼他會返回一個接收一個參數的新函數。若是咱們再傳入一個參數給這個返回的無名函數,那麼就會返回一個咱們想要的結果:.net
let addTwo = add(2) let xs = 1...100 let x = xs.map(addTwo) // x = [3, 4, 5, 6, etc]
上面的柯里化函數的另一種更容易理解的形式是:指針
func add(a: Int)(num: Int) -> Int { return a + num } //直接調用的形式與上面略有不一樣: let sum = add(2)(num: 3) // sum = 5
綜上,咱們能夠將柯里化的進行以下描述:
柯里化就是將一個接收多參數的函數,改造爲接收第一個參數,而後返回一個接收餘下參數的新方法。柯里化最大的一個好處就是代碼複用便於維護,以及量產相似的方法。
//批量產生相似方法: let addTwo = add(2) let addThree = add(3) let addFour = add(4)
咱們看看下面的簡單實例:
class BankAccount { var balance: Double = 0.0 func deposit(amount: Double) { balance += amount } }
咱們最多見的用法:
let account = BankAccount() account.deposit(100) // balance is now 100
下面看一個很是規的用法:
let depositor = BankAccount.deposit depositor(account)(100) // balance is now 200
咱們看見下面的操做實現的效果於上面的那個是同樣的。讓咱們來分析一下這個很是規的操做。首先,第一步將函數deposit函數賦值給變量depositor,咱們能夠看見BankAccount.deposit後面並無常見的括號。此時的depositor實際上是一個BankAccount -> (Double) -> ()類型的實例,有點相似於C中的函數指針。能夠在playground右側看見類型信息,見下圖。
BankAccount.deposit()傳入參數account其實就是實現綁定。
也就是說depositor傳入一個BankAccount類實例做爲參數會返回一個Double -> ()類型的函數,再傳入一個參數就能夠實現變量的疊加了。上面的很是規作法能夠進一步簡化:
BankAccount.deposit(account)(100) //balance is now 300
到目前爲止,咱們只是對一些本身的函數或者自定義類的函數上應用了柯里化,那麼如何在系統或者第三方庫中應用這種有益的特性呢?咱們看下面的例子,主要用到了拓展和泛型。
extension NSNumber { class func multiple(left: Int, right:Int) -> Int { return left * right } } func curry(function: (Int,Int) -> Int) -> (Int -> (Int -> Int)){ return { a in { b in return function(a,b) } } }
首先咱們拓展了NSNumber,定義了一個整型的乘法運算函數。而後y又定義了一個curry函數,該函數看起來很是複雜,咱們來一步步來分析。首先curry函數接受一個函數類型的參數類型爲(Int,Int) -> Int ,而後會返回一個函數類型Int -> (Int -> Int),而後再傳入一個整型給該返回類型,回繼續返回一個Int -> Int函數類型,再傳入參數纔會獲得最會結果。接下來看函數內部,return語句首先返回了一個閉包,該閉包就是Int -> (Int -> Int)類型:
{ a in //.... }
再看下一層閉包,類型是(Int -> Int),在最內層的閉包中會將上兩層閉包傳入的參數a、b進行function操做。可是這並非最通用的作法,還能改進。可使用泛型的特徵使curry函數不受參數類型限制。具體實現:
extension NSNumber { class func multiple(left: Int, right:Int) -> Int { return left * right } class func add(left:Double, right:Double) -> Double { return left + right } } func curry<A, B, C>(function: (A,B) -> C) -> (A -> (B -> C)){ return { a in { b in return function(a,b) } } } curry(NSNumber.multiple)(2)(3) // 6 curry(NSNumber.add)(2.0)(3.0) // 5.0
具體的類型能夠在playground右側查看。
熟悉瞭解柯里化的特性後,之後就能夠本身寫個高複用的類庫了,固然也可使用上篇文章中提到的Curry類庫