Swift中的柯里化Currying

引子:在上一篇文章中,咱們使用到了"Curry"。若是你看了這個框架的源代碼的話,可能有點犯暈(有可能只有我一我的這樣,你們都是大神)。這篇文章就是關於這個「柯里化」的內容,參考了庫做者的博客以及喵神的 tips 和Ole Begemann的文章。順便說一句喵神出書,能夠去支持一下。git

什麼是柯里化(Currying)

首先,咱們來看個簡單的例子: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),這意味這兩件事:函數

  1. 若是咱們直接調用該函數的化,咱們須要使用兩個括號來區分這兩個參數,形如:spa

    let sum = add(2)(3)  //sum = 5
  2. 咱們能夠經過傳遞一個參數個該函數,那麼他會返回一個接收一個參數的新函數。若是咱們再傳入一個參數給這個返回的無名函數,那麼就會返回一個咱們想要的結果:.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)

進一步理解柯里化(Currying)

咱們看看下面的簡單實例:

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右側看見類型信息,見下圖。

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類庫

相關文章
相關標籤/搜索