
Swift的各種特性已經(jīng)被很多人研究過(guò),但有一個(gè)特性只有較少人提及,那就是函數(shù)柯里化(Function Currying)。蘋(píng)果在Swift Language Guide沒(méi)有添加任何關(guān)于柯里化函數(shù)的信息,只在Swift Language Reference對(duì)它簡(jiǎn)單描述了一下。這對(duì)于一個(gè)強(qiáng)大而有用的特性來(lái)說(shuō)是很遺憾的,它值得獲得更多人關(guān)注。本文將試圖覆蓋柯里化函數(shù)的基本知識(shí)點(diǎn)和一些可能的使用場(chǎng)景,希望對(duì)你了解Swift中的函數(shù)柯里化有所幫助。
首先我假定你已經(jīng)對(duì)函數(shù)柯里化有一定了解,它在其他許多語(yǔ)言里都有實(shí)現(xiàn)。如果沒(méi)有,網(wǎng)上有不少文章解釋它是什么以及如何工作,你可以去找來(lái)看看。簡(jiǎn) 短的解釋起來(lái),函數(shù)柯里化就是,你有一個(gè)接收參數(shù)的函數(shù),你只提供給它部分的參數(shù),它不是立刻執(zhí)行而是返回給你一個(gè)新的函數(shù),這個(gè)新的函數(shù)接收剩下的參 數(shù),其內(nèi)部則指向原始函數(shù)。當(dāng)提供的參數(shù)完整了才會(huì)最終執(zhí)行原始函數(shù)。
柯里化函數(shù)的一個(gè)應(yīng)用是completion handlers。想象你有一個(gè)函數(shù),它創(chuàng)建了一個(gè)http請(qǐng)求,代碼如下:
func doGET(url: String, completionHandler: ([String]?, NSError?) -> ()) {
// do a GET HTTP request and call the completion handler when receiving the response
}
這是個(gè)大多數(shù)網(wǎng)絡(luò)庫(kù)經(jīng)常使用的模式,我們能將url作為參數(shù)來(lái)調(diào)用它,然后在completion handler做一些業(yè)務(wù)處理:
doGET("http://someurl.com/items?all=true", completionHandler: { results, error in
self.results = results
self.resultLabel.text = "Got all items"
self.tableView.reloadData()
)}
只是這樣一來(lái)completion handler會(huì)變得很復(fù)雜,而我們又想在不同的地方復(fù)用它,這時(shí)候我們可以將邏輯部分提取出來(lái),放到一個(gè)單獨(dú)的函數(shù)里,從而達(dá)到我們的目的。幸運(yùn)的 是,Swift里面函數(shù)能夠作為閉包使用,所以我們能夠直接將completion handler函數(shù)作為參數(shù)傳給doGET函數(shù):
func completionHandler(results: [String]?, error: NSError?) {
self.results = results
self.resultLabel.text = "Got all items"
self.tableView.reloadData()
}
func getAll() {
doGET("http://someurl.com/items?all=true", completionHandler)
}
func search(search: String) {
doGET("http://someurl.com/items?q=" + search, completionHandler)
}
只要completion handler只做同一件事,上面的這些代碼就能工作良好。但現(xiàn)實(shí)中,我們面對(duì)的情況經(jīng)常不是這樣。在上面的例子里,resultLabel會(huì)永遠(yuǎn)顯示“Got all items”,讓我們將它改成“Got searched items”以便在搜索請(qǐng)求中顯示:
func search(search: String) {
doGET("http://someurl.com/items?q=" + search, {results, error in
self.completionHandler(results, error: error)
self.resultLabel.text = "Got searched items"
})
}
這段代碼能正常工作,但看起來(lái)不太好。我們真正需要的是,讓它在completion handler函數(shù)中有動(dòng)態(tài)的行為。我們能夠?qū)ompletionHandler修改為接收一個(gè)text參數(shù),傳給resultLabel,然后將真正的completion handler作為一個(gè)閉包返回。
func completionHandler(text: String) -> ([String]?, NSError?) -> () {
return {results, error in
self.results = results
self.resultLabel.text = text
self.tableView.reloadData()
}
}
func getAll() {
doGET("http://someurl.com/items?all=true", completionHandler("Got all items"))
}
func search(search: String) {
doGET("http://someurl.com/items?q=" + search, completionHandler("Got searched items"))
當(dāng)整個(gè)結(jié)果出來(lái)后,我們會(huì)發(fā)現(xiàn)這恰恰是柯里化函數(shù)能做的其中一件事。如果使用函數(shù)柯里化,我們只需要將真正的completion handler的參數(shù)作為第二個(gè)參數(shù)組傳遞給我們的函數(shù)就行了:
func completionHandler(text: String)(results: [String]?, error: NSError?) {
self.results = results
self.resultLabel.text = text
self.tableView.reloadData()
}
使用第一個(gè)text參數(shù)調(diào)用函數(shù)并不會(huì)執(zhí)行,而是返回一個(gè)新的函數(shù),這個(gè)函數(shù)使用[String]?, NSError?作為參數(shù),只有當(dāng)這個(gè)函數(shù)被調(diào)用,completionHandler函數(shù)才會(huì)被真正執(zhí)行。
你能夠?qū)瘮?shù)創(chuàng)建任意多層的柯里化,你還能將最后一個(gè)參數(shù)組置空,以獲得對(duì)已接收完整參數(shù)的函數(shù)的引用。說(shuō)的有點(diǎn)繞口,還是讓代碼來(lái)說(shuō)話(huà)。我們有一個(gè)簡(jiǎn)單的函數(shù)用于設(shè)置resultLabel的text屬性:
func setResultLabelText(text: String) {
resultLabel.text = text
}
但由于某些原因,我們需要異步的調(diào)用這個(gè)方法。使用強(qiáng)大的GCD(Grand Central Dispatch)能夠?qū)崿F(xiàn)我們的要求:
dispatch_async(dispatch_get_main_queue(), {
self.setResultLabelText("Some text")
})
但dispatch_async函數(shù)只接受不帶參數(shù)的閉包作為參數(shù),我們需要為它創(chuàng)建一個(gè)內(nèi)部的閉包。但如果setResultLabelText是一個(gè)柯里化函數(shù),我們能夠?qū)?shù)傳遞給它,然后獲得一個(gè)不帶參數(shù)的函數(shù)的引用,這樣我們就能直接在dispatch_async函數(shù)中使用它了。
func setResultLabelText(text: String)() { // now curried
resultLabel.text = text
}
dispatch_async(dispatch_get_main_queue(), setResultLabelText("Some text"))
上面的代碼看起來(lái)很不錯(cuò),但你并不是總有權(quán)限去直接修改函數(shù),比如當(dāng)使用第三方庫(kù)的時(shí)候。這種情況下你不能將原始函數(shù)轉(zhuǎn)換為一個(gè)柯里化函數(shù),或者你 已經(jīng)在很多其他的地方用過(guò)這個(gè)函數(shù),所以不好修改它。不過(guò)我們還是有辦法的,通過(guò)創(chuàng)建一個(gè)新的函數(shù)并將其柯里化,我們能夠達(dá)到類(lèi)似的目的:
// defined in global scope
func curry<T>(f: (T) -> (), arg: T)() {
f(arg)
}
現(xiàn)在我們可以這么做:
func setResultLabelText(text: String) {
resultLabel.text = text
}
dispatch_async(dispatch_get_main_queue(), curry(setResultLabelText, "Some text"))
在這個(gè)示例里,它可能和使用內(nèi)部閉包的難易度差不多,但能夠?qū)⒔邮樟瞬糠謪?shù)的函數(shù)到處傳送,這樣的功能是非常強(qiáng)大的,并且已經(jīng)在很多編程語(yǔ)言里得到應(yīng)用。
不幸的是,最后的示例同時(shí)也展示了Swift中實(shí)現(xiàn)柯里化函數(shù)的缺點(diǎn):你不能簡(jiǎn)單的轉(zhuǎn)換一般函數(shù)。如果能將任意接受多參數(shù)函數(shù)轉(zhuǎn)換為柯里化函數(shù),而 非創(chuàng)建一個(gè)新的函數(shù)那該是多好啊。另一個(gè)缺點(diǎn)是只能按定義的參數(shù)順序來(lái)柯里化函數(shù),這將不能讓你執(zhí)行反柯里化(比如只接受最后一個(gè)參數(shù))或只提供你想提供 的參數(shù)。希望Swift能夠在未來(lái)的演化中覆蓋這些,并提供更強(qiáng)大的柯里化特性。