新聞中心
如果你開(kāi)始接觸函數(shù)式編程,你一定聽(tīng)說(shuō)過(guò)高階函數(shù)。在維基百科它的中文解釋是這樣的:

創(chuàng)新互聯(lián)建站是一家集網(wǎng)站建設(shè),東洲企業(yè)網(wǎng)站建設(shè),東洲品牌網(wǎng)站建設(shè),網(wǎng)站定制,東洲網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷(xiāo),網(wǎng)絡(luò)優(yōu)化,東洲網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
在數(shù)學(xué)和計(jì)算機(jī)科學(xué)中,高階函數(shù)是至少滿足下列一個(gè)條件的函數(shù):
- 接受一個(gè)或多個(gè)函數(shù)作為輸入
- 輸出一個(gè)函數(shù)
看起它就是ObjC語(yǔ)言中入?yún)⒒蛘叻祷刂禐閎lock的block或者函數(shù),在Swift語(yǔ)言中即為入?yún)⒒蛘叻祷刂禐楹瘮?shù)的函數(shù)。那它們?cè)趯?shí)際的開(kāi)發(fā)過(guò)程中究竟起著什么樣的作用呢?我們將從入?yún)ⅰ⒎祷刂岛途C合使用三部分來(lái)看這個(gè)問(wèn)題:
函數(shù)作為入?yún)?/strong>
函數(shù)作為入?yún)⑺坪鯚o(wú)論在ObjC時(shí)代還是Swift時(shí)代都是司空見(jiàn)慣的事情,例如AFNetworking就用兩個(gè)入?yún)lock分別回調(diào)成功與失敗。Swift中更是加了一個(gè)尾閉包的語(yǔ)法(最后一個(gè)參數(shù)為函數(shù),可以不寫(xiě)括號(hào)或者寫(xiě)到括號(hào)外面直接跟隨方法名),例如下面這樣:
- [1, 2, 3].forEach { item in
- print(item)
- }
我們可以將入?yún)楹瘮?shù)的函數(shù)分為兩類(lèi),escaping函數(shù)入?yún)⒑蚽oescape函數(shù)入?yún)ⅲ瑓^(qū)別在于這個(gè)入?yún)⒌暮瘮?shù)是在執(zhí)行過(guò)程內(nèi)被調(diào)用還是在執(zhí)行過(guò)程外被調(diào)用。執(zhí)行過(guò)程外被調(diào)用的一般用于callback用途,例如:
- Alamofire.request("https://httpbin.org/get").responseJSON { response in
- print(response.request) // original URL request
- print(response.response) // HTTP URL response
- print(response.data) // server data
- print(response.result) // result of response serialization
- if let JSON = response.result.value { print("JSON: \(JSON)")
- }
- }
這個(gè)response的入?yún)⒑瘮?shù)就作為網(wǎng)絡(luò)請(qǐng)求回來(lái)的一個(gè)callback,并不會(huì)在執(zhí)行responseJSON這個(gè)函數(shù)的時(shí)候被調(diào)用。另外我們來(lái)觀察forEach的代碼,可以推斷入?yún)⒌暮瘮?shù)一定會(huì)在forEach執(zhí)行過(guò)程中使用,執(zhí)行完就沒(méi)有利用意義,這類(lèi)就是noescape函數(shù)。
callback的用法大家應(yīng)該比較熟悉了,介紹給大家noescape入?yún)⒌囊恍┯梅ǎ?/p>
1. 自由構(gòu)造器
看過(guò)GoF設(shè)計(jì)模式的同學(xué)不知道是否還記得構(gòu)造器模式,Android中的構(gòu)造器模式類(lèi)似如下:
- new AlertDialog.Builder(this)
- .setIcon(R.drawable.find_daycycle_icon)
- .setTitle("提醒")
- .create()
- .show();
- 構(gòu)造一個(gè)對(duì)象需要很多的參數(shù)
- 這些參數(shù)里面很多有默認(rèn)值
- 這些參數(shù)對(duì)應(yīng)的屬性未來(lái)不希望被修改
那么用這樣的模式就可以直觀又精巧的展示構(gòu)建過(guò)程。
如果使用noescape入?yún)⒑瘮?shù)還可以更簡(jiǎn)單的構(gòu)造出這種代碼,只需要傳入一個(gè)入?yún)閎uilder的對(duì)象就可以了,如下:
- // 實(shí)現(xiàn)在這里 class SomeBuilder { var prop1: Int
- var prop2: Bool
- var prop3: String
- init() { // default value
- prop1 = 0
- prop2 = true
- prop3 = "some string"
- }
- }class SomeObj {
- private var prop1: Int
- private var prop2: Bool
- private var prop3: String
- init(_ builderBlock:(SomeBuilder) -> Void) { let someBuilder = SomeBuilder()
- builderBlock(someBuilder) // noescape 入?yún)⒌氖褂?nbsp;
- prop1 = someBuilder.prop1
- prop2 = someBuilder.prop2
- prop3 = someBuilder.prop3
- }
- }// 使用的時(shí)候 let someOjb = SomeObj { builder in
- builder.prop1 = 15
- builder.prop2 = false
- builder.prop3 = "haha"}
2. 自動(dòng)配對(duì)操作
很多時(shí)候,我們開(kāi)發(fā)過(guò)程中都會(huì)遇到必須配對(duì)才能正常工作的API,例如打開(kāi)文件和關(guān)閉文件、進(jìn)入edit模式退出edit模式等。雖然swift語(yǔ)言給我們defer這樣的語(yǔ)法糖避免大家忘記配對(duì)操作,但是代碼看起來(lái)還是不那么順眼
- func updateTableView1() { self.tableView.beginUpdates() self.tableView.insertRows(at: [IndexPath(row: 2, section: 0)], with: .fade) self.tableView.deleteRows(at: [IndexPath(row: 5, section: 0)], with: .fade) self.tableView.endUpdates() // 容易漏掉或者上面出現(xiàn)異常 }func updateTableView2() { self.tableView.beginUpdates()
- defer { self.tableView.endUpdates()
- } self.tableView.insertRows(at: [IndexPath(row: 2, section: 0)], with: .fade) self.tableView.deleteRows(at: [IndexPath(row: 5, section: 0)], with: .fade)
- }
利用noescape入?yún)ⅲ覀兛梢詫⒁僮鞯倪^(guò)程封裝起來(lái),使得上層看起來(lái)更規(guī)整
- // 擴(kuò)展一下UITableView extension UITableView { func updateCells(updateBlock: (UITableView) -> Void) {
- beginUpdates()
- defer {
- endUpdates()
- }
- updateBlock(self)
- }
- }func updateTableView() { // 使用的時(shí)候
- self.tableView.updateCells { (tableView) in
- tableView.insertRows(at: [IndexPath(row: 2, section: 0)], with: .fade)
- tableView.deleteRows(at: [IndexPath(row: 5, section: 0)], with: .fade)
- }
- }
函數(shù)作為入?yún)⒕秃?jiǎn)單介紹到這里,下面看看函數(shù)作為返回值。
函數(shù)作為返回值
在大家的日常開(kāi)發(fā)中,函數(shù)作為返回值的情況想必是少之又少。不過(guò),如果能簡(jiǎn)單利用起來(lái),就會(huì)讓代碼一下子清爽很多。
首先沒(méi)有爭(zhēng)議的就是我們有很多的API都是需要函數(shù)作為入?yún)⒌?,無(wú)論是上一節(jié)提到過(guò)的escaping入?yún)⑦€是noescape入?yún)?。所以很多的時(shí)候,大家寫(xiě)的代碼重復(fù)率會(huì)很高,例如:
- let array = [1, 3, 55, 47, 92, 77, 801]let array1 = array.filter { $0 > 3 * 3}let array2 = array.filter { $0 > 4 * 4}let array3 = array.filter { $0 > 2 * 2}let array4 = array.filter { $0 > 5 * 5}
一段從數(shù)組中找到大于某個(gè)數(shù)平方的代碼,如果不封裝,看起來(lái)應(yīng)該是這樣的。為了簡(jiǎn)化,通常會(huì)封裝成如下的兩個(gè)樣子:
- func biggerThanPowWith(array: [Int], value: Int) -> [Int] {
- return array.filter { $0 > value * value}
- }
- let array1 = biggerThanPowWith(array: array, value: 3)
- let array2 = biggerThanPowWith(array: array, value: 4)
- let array3 = biggerThanPowWith(array: array, value: 2)
- let array4 = biggerThanPowWith(array: array, value: 5)
如果用高階函數(shù)的返回值函數(shù),可以做成這樣一個(gè)高階函數(shù):
- // 一個(gè)返回(Int)->Bool的函數(shù) func biggerThanPow2With(value: Int) -> (Int) -> Bool { return { $0 > value * value }
- }let array1 = array.filter(biggerThanPow2With(value: 3))let array2 = array.filter(biggerThanPow2With(value: 4))let array3 = array.filter(biggerThanPow2With(value: 2))let array4 = array.filter(biggerThanPow2With(value: 5))
你一定會(huì)說(shuō),兩者看起來(lái)沒(méi)啥區(qū)別。所以這里面需要講一下使用高階返回函數(shù)的幾點(diǎn)好處
1. 不需要wrapper函數(shù)也不需要打開(kāi)原始類(lèi)
如同上面的簡(jiǎn)單封裝,其實(shí)就是一個(gè)wrapper函數(shù),把a(bǔ)rray作為入?yún)脒M(jìn)來(lái)。這樣寫(xiě)代碼和看代碼的時(shí)候就稍微不爽一點(diǎn),畢竟大家都喜歡OOP嘛。如果要OOP,那就勢(shì)必要對(duì)原始類(lèi)進(jìn)行擴(kuò)展,一種方式是加extension,或者直接給類(lèi)加一個(gè)新的方法。
2. 閱讀代碼的時(shí)候一目了然
使用簡(jiǎn)單封裝的時(shí)候,看代碼的人并不知道內(nèi)部使用了filter這個(gè)函數(shù),必須要查看源碼才能知道。但是用高階函數(shù)的時(shí)候,一下子就知道了使用了系統(tǒng)庫(kù)的filter。
3. 更容易復(fù)用
這也是最關(guān)鍵的一點(diǎn),更細(xì)粒度的高階函數(shù),可以更方便的復(fù)用,例如我們知道Set也是有filter這個(gè)方法的,復(fù)用起來(lái)就這樣:
- let set = Set
(arrayLiteral: 1, 3, 7, 9, 17, 55, 47, 92, 77, 801)let set1 = set.filter(biggerThanPow2With(value: 3))let set2 = set.filter(biggerThanPow2With(value: 9))
回憶下上面的簡(jiǎn)單封裝,是不是就無(wú)法重用了呢?
類(lèi)似的返回函數(shù)的高階函數(shù)還可以有很多例子,例如上面說(shuō)過(guò)的builder,假如每次都需要定制成特殊的樣子,但是某個(gè)字段不同,就可以用高階函數(shù)很容易打造出來(lái):
- func builerWithDifferentProp3(prop3: String) -> (SomeBuilder) -> Void { return { builder in
- builder.prop1 = 15
- builder.prop2 = true
- builder.prop3 = prop3
- }
- }let someObj1 = SomeObj.init(builerWithDifferentProp3(prop3: "a"))let someObj2 = SomeObj.init(builerWithDifferentProp3(prop3: "b"))let someObj3 = SomeObj.init(builerWithDifferentProp3(prop3: "c"))
介紹完入?yún)⑴c返回值,還有另外的一個(gè)組合模式,那就是入?yún)⑹且粋€(gè)函數(shù),返回值也是一個(gè)函數(shù)的情況,我們來(lái)看看這種情況。
入?yún)⒑瘮?shù) && 返回值函數(shù)
這樣的一個(gè)函數(shù)看起來(lái)會(huì)很恐怖,swift會(huì)聲明成:
- func someFunc(_ a: (A) -> B)-> (C) -> D
objective-c會(huì)聲明成
- - (id (^)(id))someFunc:(id (^)(id))block
讓我們先從一個(gè)小的例子來(lái)講起,回憶一下我們剛剛做的biggerThanPow2With這個(gè)函數(shù),如果我們要一個(gè)notBiggerThanPow2With怎么辦呢?你知道我一定不會(huì)說(shuō)再寫(xiě)一個(gè)。所以我告訴你我會(huì)這樣寫(xiě):
- func not
(_ origin_func: @escaping (T) -> Bool) -> (T) -> Bool { return { !origin_func($0) } - }let array5 = array.filter(not(biggerThanPow2With(value: 9)))
并不需要一個(gè)notBiggerThanPow2With函數(shù),我們只需要實(shí)現(xiàn)一個(gè)not就可以了。它的入?yún)⑹且粋€(gè)(T) -> Bool,返回值也是(T) -> Bool,只需要在執(zhí)行block內(nèi)部的時(shí)候用個(gè)取反就可以了。這樣不單可以解決剛才的問(wèn)題,還可以解決任何(T) -> Bool類(lèi)型函數(shù)的取反問(wèn)題,比如我們有一個(gè)odd(_: int)方法來(lái)過(guò)濾奇數(shù),那我們就可以用even=not(odd)得到一個(gè)過(guò)濾偶數(shù)的函數(shù)了。
- func odd(_ value: Int) -> Bool { return value % 2 == 1}let array6 = array.filter(odd)let array7 = array.filter(not(odd))let even = not(odd)let array8 = array.filter(even)
大家可以看下上面的biggerThanPow2With時(shí)我們討論過(guò)的,如果biggerThanPow2With不是一個(gè)返回函數(shù)的高階函數(shù),那它就不太容易用not函數(shù)來(lái)加工了。
綜上,如果一個(gè)入?yún)⒑头祷刂刀际呛瘮?shù)的函數(shù)就是這樣的一個(gè)轉(zhuǎn)換函數(shù),它能夠讓我們用更少的代碼組合出更多的函數(shù)。另外需要注意一下,如果返回的函數(shù)里面閉包了入?yún)⒌暮瘮?shù),那么入?yún)⒑瘮?shù)就是escaping入?yún)⒘恕?/p>
下面再展示給大家兩個(gè)函數(shù),一個(gè)交換參數(shù)的函數(shù)exchangeParam,另一個(gè)是柯里化函數(shù)currying:
swift語(yǔ)言里面>是一個(gè)入?yún)?a, b)的函數(shù),所以>(5, 3) == true。我們使用exchangeParam交換參數(shù)就變成了(b, a),這時(shí)exchangeParam(>)(5, 3)就等于false了。
而currying函數(shù)又把參數(shù)b固定為一個(gè)常量9,所以currying(exchangeParam(>), 9)就是大于9的函數(shù)意思。
這個(gè)例子里就利用了全部的預(yù)制函數(shù)和通用函數(shù),沒(méi)有借助任何的命令與業(yè)務(wù)函數(shù)聲明實(shí)現(xiàn)了一個(gè)從數(shù)組中過(guò)濾大于9的子數(shù)組的需求。試想一下,如果我們更多的使用這樣的高階函數(shù),代碼中是不是很多的邏輯可以更容易的互相組合,而這就是函數(shù)式編程的魅力。
總結(jié)
高階函數(shù)的引入,無(wú)論是從函數(shù)式編程還是從非函數(shù)式編程都帶給我們代碼一定程度的簡(jiǎn)化,使得我們的API更加簡(jiǎn)易可用,復(fù)用更充分。然而本文的例子不過(guò)是冰山一角,更多的內(nèi)容還需要大家的不斷嘗試和創(chuàng)新,也可以通過(guò)學(xué)習(xí)更多的函數(shù)式編程范式來(lái)加深理解。
作者介紹:
網(wǎng)站題目:不學(xué)點(diǎn)高階函數(shù),如何愉快的裝逼!
網(wǎng)頁(yè)地址:http://m.5511xx.com/article/dppeeci.html


咨詢
建站咨詢
