Swift 柯裏化
前言
- 由於柯裏化在業務層的應用較少,所以從 Swift 3.0 開始移除了柯裏化的用法,但是 Swift 的很多底層特性是使用柯裏化來表達的。
1、柯裏化
1.1 柯裏化簡介
柯裏化(Currying),又稱部分求值(Partial Evaluation),是一種函數式編程思想,就是把接受多個參數的函數轉換成接收一個單一參數(最初函數的第一個參數)的函數,並且返回一個接受余下參數的新函數技術。
uncurried: 普通函數
// 接收多個參數的函數(與類相關的函數,統稱為方法,但是這裏就直接說函數了,方便理解) func add(a: Int, b: Int, c: Int) -> Int { print("\(a) + \(b) + \(c)") return a + b + c }
curried: 柯裏化函數
// 柯裏化函數,Swift 3.0 之前支持這樣的語法,可以直接寫 func addCur(a: Int)(b: Int)(c: Int) -> Int { println("\(a) + \(b) + \(c)") return a + b + c }
1.2 如何定義柯裏化函數
定義柯裏化函數
func function name (parameters)(parameters) -> return type { statements }
1.3 柯裏化函數實現原理
uncurried: 普通函數
class Currying { // 接收多個參數的函數 func add(a: Int, b: Int, c: Int) -> Int { print("\(a) + \(b) + \(c)") return a + b + c } }
系統自帶的柯裏化函數
class Currying { func addCur(a: Int)(b: Int)(c: Int) -> Int { print("\(a) + \(b) + \(c)") return a + b + c } }
手動實現柯裏化函數
把上面的函數轉換為柯裏化函數,首先轉成接收第一個參數
a
,並且返回接收余下第一個參數b
的新函數(采用閉包).這裏為什麽能使用參數
a、b、c
?- 利用閉包的值捕獲特性,即使這些值作用域不在了,也可以捕獲到他們的值。
- 閉包會自動判斷捕獲的值是值拷貝還是值引用,如果修改了,就是值引用,否則值拷貝。
- 註意只有在閉包中才可以,
a、b、c
都在閉包中。
class Currying { // (a: Int) : 參數 // (b: Int) -> (c: Int) -> Int : 函數返回值(一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c, // 返回值為 Int 類型的函數) // 定義一個接收參數 a,並且返回一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數。 func add(a: Int) -> (b: Int) -> (c: Int) -> Int { // 返回一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數 return { (b:Int) -> (c: Int) -> Int in // 返回一個接收余下第一個參數 c,並且返回結果為 Int 類型的函數 return { (c: Int) -> Int in return a + b + c; } } } }
1.4 如何調用柯裏化函數
創建柯裏化類的實例
var curryInstance = Currying()
手動實現的柯裏化函數調用
var result: Int = curryInstance.add(a: 10)(b: 20)(c: 30)
- 可能很多人都是第一次看這樣的調用,感覺有點不可思議。
- 讓我們回顧下 OC 創建對象
[[Person alloc] init]
,這種寫法應該都見過吧,就是一下發送了兩個消息,alloc
返回一個實例,再用實例調用init
初始化,上面也是一樣,一下調用多個函數,每次調用都會返回一個函數,然後再次調用這個返回的函數。
手動實現的柯裏化函數拆解調用
curryInstance.add(a: 10)
調用一個接收參數a
,並且返回一個接收參數b
的函數,並且這個函數又返回一個接收參數c
,返回值為Int
類型的函數。// functionB: 一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數 let functionB = curryInstance.add(a: 10)
functionB(b: 20)
調用一個接收參數b
的函數,並且這個函數又返回一個接收參數c
,返回值為Int
類型的函數。// functionC: 一個接收參數 c,返回值為 Int 類型的函數 let functionC = functionB(b: 20)
functionC(c: 30)
調用一個接收參數c
,返回值為Int
類型的函數。// result: 函數的返回值 var result: Int = functionC(c: 30);
系統的柯裏化函數調用
var result: Int = curryInstance.addCur(a: 10)(b: 20)(c: 30)
系統的柯裏化函數拆解調用
curryInstance.addCur(a: 10)
調用一個接收參數a
,並且返回一個接收參數b
的函數,並且這個函數又返回一個接收參數c
,返回值為Int
類型的函數。// Swift是強類型語言,這裏沒有報錯,說明調用系統柯裏化函數返回的類型和手動的 functionB 類型一致 // functionB: 一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數 functionB = curryInstance.addCur(a: 10)
functionB(b: 20)
調用一個接收參數b
的函數,並且這個函數又返回一個接收參數c
,返回值為Int
類型的函數。// functionC: 一個接收參數c,返回值為Int類型的函數 functionC = functionB(b: 20)
functionC(c: 30)
調用一個接收參數c
,返回值為Int
類型的函數。// result: 函數的返回值 result = functionC(c: 30) // 打印 60,60,60 說明手動實現的柯裏化函數,和系統的一樣。 print("\(r), \(res), \(result)")
1.5 柯裏化函數使用註意
必須按照參數的定義順序來調用柯裏化函數,否則就會報錯。
柯裏化函數的函數體只會執行一次,只會在調用完最後一個參數的時候執行柯裏化函數體。
以下調用
functionC(c: 30)
才會執行函數體,這個可以自己斷點調試。// curried: 柯裏化函數 func addCur(a: Int)(b: Int)(c: Int) -> Int { println("\(a) + \(b) + \(c)") return a + b + c } // 創建柯裏化類的實例 var curryInstance = Currying() // 不會執行柯裏化函數體 functionB = curryInstance.addCur(a: 10) // 不會執行柯裏化函數體 functionC = functionB(b: 20) // 執行柯裏化函數體 result = functionC(c: 30)
1.6 柯裏化函數的好處
這裏就需要了解函數式編程思想了,柯裏化函數就是運用了函數式編程思想,推薦看這篇文章函數式編程初探。
特點
- 1)只用 “表達式” (表達式: 單純的運算過程,總是有返回值),不用 “語句” (語句: 執行某種操作,沒有返回值)。
- 2)不修改值,只返回新值。
好處
- 1)代碼簡潔。
- 2)提高代碼復用性。
- 3)代碼管理方便,相互之間不依賴,每個函數都是一個獨立的模塊,很容易進行單元測試。
- 4)易於 “並發編程”,因為不修改變量的值,都是返回新值。
1.7 柯裏化函數的實用性
實用性一:復用性
需求 1:地圖類產品,很多界面都有相同的功能模塊,比如搜索框。
- 我們可以利用柯裏化函數,來組裝界面,把界面分成一個個小模塊,這樣其他界面有相同的模塊,直接運用模塊代碼,去重新組裝下就好了。
實用性二:延遲性
柯裏化函數代碼需要前面的方法調用完成之後,才會來到柯裏化函數代碼中。
需求 2:閱讀類產品,一個界面的顯示,依賴於數據,需要加載完數據之後,才能判斷界面顯示。
- 這時候也可以利用柯裏化函數,來組裝界面,把各個模塊加載數據的方法抽出來,等全部加載完成,再去執行柯裏化函數,柯裏化函數主要實現界面的組裝。
舉例說明
// 組合接口 // 為什麽要定義接口,為了程序的擴展性,以後只需要在接口中添加對應的組合方法就好了。 protocol CombineUI { func combine(top: () -> ())(bottom: () -> ())() } // 定義一個界面類,遵守組合接口 class UI: CombineUI { func combine(top: () -> ())(bottom: () -> ())() { // 搭建頂部 top() // 搭建底部 bottom() } }
2、Swift 中實例方法的柯裏化
- Swift 中實例方法就是一個柯裏化函數。
2.1 Swift 中實例方法的柯裏化調用
Swift 中實例方法的柯裏化調用
示例結構體
struct Example { var internalStr = "" func combine(externalStr: String) { print(internalStr + " " + externalStr) } }
調用實例方法的常用格式
let example = Example(internalStr: "hello") example.combine(externalStr: "word") // hello word
調用實例方法的柯裏化格式
let example = Example(internalStr: "hello") Example.combine(example)(externalStr: "word") // hello word
Swift 柯裏化